Really simple problem where when I hover over the navigation bar of my page (in a unordered list), the tab is supposed to update with a new value. The value updates, but the attributing HTML markup does not render along with it (such as when I tried to make the 'why' value bold. I tried even storing both of the values in two variables and concat together, but still no luck. Any help would be appreciated.
Screenshot of Nav Bar (Vertical):
My Default Props:
getDefaultProps: function(){
var text = '<i className="fa fa-laptop"></i>';
var text2 = '<b>why<b>';
return{why: text + text2};
},
Line needing to be changed:
<li value = '3' className = "nav-content" style = {ulTop} onMouseEnter = {this.change} onMouseLeave = {this.out} ref = 'whyTab'><Link to = '/Why'><i className="fa fa-code"></i>{this.props.why}</Link></li>
Related
I was trying to test a few novice tricks from a project tutorial. Wanted to create a small scale task app and ran into a weird problem. The last document.addEventListener below should theoretically call the closest element with the class name of ".name" should be detected since its in the same parent div with the button. However it is returning NULL. Am I applying the .closest() method wrong?
The event listener detects the button after everytime a task is created. Not sure why it returns NULL when, after creating the task via addTaskButton, the task with the class name of ".name". I even tried to create a data attribute id based off of the taskName itself to see if it'll detect, but still NULL / erroring.
const list = []
const container = document.querySelector('.container');
const itemContainer = document.querySelector('.item');
const addTaskButton = document.querySelector('.add-task');
const taskInput = document.querySelector('#task-name');
function renderTasks(){
itemContainer.innerHTML = ''
list.forEach(task => {
const itemElement = document.createElement('div')
itemElement.innerHTML = `
<div class="name">
${task.taskName}
</div>
<button class="retrieval">Retrieve ID</button>
`
itemElement.dataset.itemName = task.taskName
itemContainer.appendChild(itemElement);
})
}
addTaskButton.addEventListener('click', (e)=>{
e.preventDefault();
list.push({ taskName: taskInput.value})
renderTasks()
})
document.addEventListener('click', (e)=>{
if(e.target.matches('.retrieval')){
const taskName = e.target.closest('.name');
console.log(taskName)
}
})
Ok, I double checked the mdn article it says:
closestElement is the Element which is the closest ancestor of the
selected element. It may be null.
That means it only looks for parents and parents of parents and so on, not 'siblings'.
I have a Master-Detail ag-grid. One column has checkboxes, (checkboxSelection: true). The details grid have a custom status panel with a button. When the user clicks the button in any specific Detail grid, I don't know how to get the SelectedRows from just that one specific detail grid.
The problem is they might leave multiple details displayed/open, and then looping over each Detail Grid will include results from all open grids. I'm trying to isolate to just the grid where the user clicked the button.
I tried looping through all displayed/open detail grids to get the Detail grid ID. But I don't see any info in this that shows me which one they clicked the button in.
I tried in the button component to see if, in the params, there is anything referencing the detailgrid ID that the button is in, but I did not see anything there either.
This is the button component:
function ClickableStatusBarComponent() {}
ClickableStatusBarComponent.prototype.init = function(params)
{
this.params = params;
this.eGui = document.createElement('div');
this.eGui.className = 'ag-name-value';
this.eButton = document.createElement('button');
this.buttonListener = this.onButtonClicked.bind(this);
this.eButton.addEventListener("click", this.buttonListener);
this.eButton.innerHTML = 'Cancel Selected Records <em class="fas fa-check" aria-hidden="true"></em>';
console.log(this.params);
this.eGui.appendChild(this.eButton);
};
ClickableStatusBarComponent.prototype.getGui = function()
{
return this.eGui;
};
ClickableStatusBarComponent.prototype.destroy = function()
{
this.eButton.removeEventListener("click", this.buttonListener);
};
ClickableStatusBarComponent.prototype.onButtonClicked = function()
{
getSelectedRows();
};
Here is the code to loop through and find all open detail grids:
function getSelectedRows()
{
this.gridOptions.api.forEachDetailGridInfo(function(detailGridApi) {
console.log(detailGridApi.id);
});
I was able to work this out, so thought I'd post my answer in case others have the same issue. I'm not sure I took the best approach, but it's seemingly working as I need.
First, I also tried using a custom detail cell renderer, as per the documentation, but ultimately had the same issue. I was able to retrieve the DetailGridID in the detail onGridReady function--but couldn't figure out how to use that variable elsewhere.
So I went back to the code posted above, and when the button was clicked, I do a jquery .closest to find the nearest div with a row-id attribute (which represents the the DetailgridID), then I use that specific ID to get the rows selected in just that detail grid.
Updated button click code:
ClickableStatusBarComponent.prototype.onButtonClicked = function()
{
getSelectedRows(this);
};
Updated getSelectedRow function:
function getSelectedRows(clickedBtn)
{
var detailGridID = $(clickedBtn.eButton).closest('div[row-id]').attr('row-id');
var detailGridInfo = gridOptions.api.getDetailGridInfo(detailGridID);
const selectedNodes = detailGridInfo.api.getSelectedNodes()
const selectedData = selectedNodes.map( function(node) { return node.data })
const selectedDataStringPresentation = selectedData.map( function(node) {return node.UniqueID}).join(', ')
console.log(selectedDataStringPresentation);
}
I am using the shinyJS package as well as shiny tags and traditional HTML to add the attribute 'title' to existing elements. I am able to do this without issue within the console (inspector [Google CHROME]) but when attempting to apply the same inputs via either ui.R or server.R, either nothing changes or the actual text value changes vice adding title (tooltip).
As mentioned above, I have tried the following:
shinyJS: html()
shiny: tags$HTML; tags$body(tags$script())
HTML: Adding the change in HTML file (mychange.html) and sourcing from www
The input to modify (add tooltip)
pickerInput(
inputid = "ReqTabSel6",
label = '',
choices = c('Choice 1', 'Choice 2', 'Choice 3'),
mulitple = F,
options = list(
style = "btn-info"))
Here is the correct function (as it updates when running in console under web inspector):
var addToolTip1 = document.querySelector('#form>div:nth-child(10)>div>div>div>ul>li.selected>a')
var att = document.createAttribute("title");
att.value = "I am a tooltip title";
addToolTip1 .setAttributeNode(att);
However, in R...
shinyJS
server.R
...
observeEvent(input$ReqTabSel6, {
shinyJS::html(id = NULL,
html = "var addToolTip1 = document.querySelector('#form>div:nth-child(10)>div>div>div>ul>li.selected>a')
var att = document.createAttribute("title");
att.value = "I am a tooltip title";
addToolTip1 .setAttributeNode(att);",
selector = '#form>div:nth-child(10)>div>div>div>ul>li.selected>a')
})
##This updates the actual 'choice' value from 'Choice 1' to 'Choice1I am a tooltip title')
#Changing html to read:
html = 'title = "I am a tooltip title"',
#This replaces the choice (e.g. Choice 1) value so the drop down now has:
I am a tooltip title
Choice 2
Choice 3
Would like to create the tooltip for each child (tab index) of the pickerInput choices. Should just add 'title' attribute to other attributes within the designated node.
Got it! Answering it myself in case another user runs in to this.
#server.R
observeEvent(input$actionbutton1,{
shinyjs::html(
html='<a tabindex="0" class=""...title="Your tooltip here"...></a>',
add=FALSE,
selector = '#form>div>form>div>div:nth-child(5)>div>div>div>ul>li:nth-child(4)' #This is the jquerySelector. Note that it will be different for each unique application. You will need to change the 'nth-child(#)' to the specific choice within 'SelectInput' to get a tooltip specific to that choice.)
})
I'm playing around with a webapp using React. My code currently creates a list of buttons that can change the color of a circle, depending on the randomly-generated button the user clicks on. To enhance my code, when the user clicks the button, I want all of the buttons colors to change, and therefore need to update all of the button's onClick functions, since they pass their color as an argument to the function that changes the circle's color. Below is the solution I currently have: it requires me to remove every button, and then completely reconstruct the button. Just using button.onclick = function() { newOnclickFunction} does not work, and I have not been able to find the answer on my own. Any help would be greatly appreciated; I'm almost certain there's a better a way to do it than this.
let reflipPalleteCompletely = () => {
let everyButtonPossible = document.getElementsByClassName("colorChangeButton")
for( var button of everyButtonPossible){
button.style.backgroundColor = randomColor()
let myParent = button.parentElement
myParent.removeChild(button)
let freshButton = document.createElement("button", {value: "Click", className: "colorChangeButton", })
freshButton.innerHTML = 'Click'
freshButton.className = 'colorChangeButton'
freshButton.style.backgroundColor = randomColor()
let newOnclickFunction = () => { changeToNewColor(button.style.backgroundColor); reflipPalleteCompletely() }
freshButton.onclick = function() { newOnclickFunction() }
myParent.appendChild(freshButton)
The short answer is: if each button is supposed to change color to reflect the color value it represents, and that set of colors is re-randomized every time a button is pressed, then you can't and shouldn't avoid re-render of the buttons.
However, Mike is right: you're not using React properly if you're writing your own element-creation scripts.
Your component might look like this:
const buttonCount = 10
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
currentColor: getRandomColor()
}
}
setColor = (newColor, event) => {
this.setState({
currentColor: newColor
})
}
render() {
let {
currentColor
} = this.props
let colorChoices = Array(buttonCount)
.fill()
.map(() => getRandomColor())
return (
<div className="Demo">
<div className="the-shape" style={{ backgroundColor: currentColor }} />
<ol className="color-choices">
{
colorChoices.map( color => (
<button key={ color }
style={{ backgroundColor: color }}
onClick={ this.setColor.bind(this, color) }
>
{ color }
</button>
))
}
</ol>
</div>
)
}
}
This all depends on you having a getRandomColor function that can generate a color. And it doesn't address making sure that the choices don't include the current color (although you could easily do that by e.g. generating 2n colors, filtering out the currentColor, then taking the first n, or somesuch).
If you really hate redrawing the buttons, you could save their refs and then have the setColor method iterate through them and modify their styles.
But the point of React is to avoid procedural mutation of the DOM in favor of declaring the desired DOM and letting the React engine figure out an efficient mutation strategy.
A direct answer to the question you asked: "what's a more optimal way to change out an HTML element's.onClick element?" might be: find a pattern that doesn't require you to change the function every time.
Instead of having this:
let newOnclickFunction = () => { changeToNewColor(button.style.backgroundColor); reflipPalleteCompletely() }
Try something like this instead:
function onClickButton(event) {
let button = event.target
let color = button.style.backgroundColor
changeToNewColor(color)
}
This way, the desired color value isn't baked into the onClick function. Instead, the function examines the button whose click invoked it, and uses its background as the argument to changeToNewColor.
With some clever CSS, you could write the desired color to a data- prop on each button, and have the browser do the work of calculating background-color from that. Then you could use event delegation on some ancestor element that contains all the buttons, that listens for a click on any element with that data- prop and does the same work as above. This way, you don't even have a click function on each button.
I would like to use a javascript loop to create multiple HTML wrapper elements and insert JSON response API data into some of the elements (image, title, url, etc...).
Is this something I need to go line-by-line with?
<a class="scoreboard-video-outer-link" href="">
<div class="scoreboard-video--wrapper">
<div class="scoreboard-video--thumbnail">
<img src="http://via.placeholder.com/350x150">
</div>
<div class="scoreboard-video--info">
<div class="scoreboard-video--title">Pelicans # Bulls Postgame: E'Twaun Moore 10-8-17</div>
</div>
</div>
</a>
What I am trying:
var link = document.createElement('a');
document.getElementsByTagName("a")[0].setAttribute("class", "scoreboard-video-outer-link");
document.getElementsByTagName("a")[0].setAttribute("url", "google.com");
mainWrapper.appendChild(link);
var videoWrapper= document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video-outer-link");
link.appendChild(videoWrapper);
var videoThumbnailWrapper = document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video--thumbnail");
videoWrapper.appendChild(videoThumbnailWrapper);
var videoImage = document.createElement('img');
document.getElementsByTagName("img")[0].setAttribute("src", "url-of-image-from-api");
videoThumbnailWrapper.appendChild(videoImage);
Then I basically repeat that process for all nested HTML elements.
Create A-tag
Create class and href attributes for A-tag
Append class name and url to attributes
Append A-tag to main wrapper
Create DIV
Create class attributes for DIV
Append DIV to newly appended A-tag
I'd greatly appreciate it if you could enlighten me on the best way to do what I'm trying to explain here? Seems like it would get very messy.
Here's my answer. It's notated. In order to see the effects in the snippet you'll have to go into your developers console to either inspect the wrapper element or look at your developers console log.
We basically create some helper methods to easily create elements and append them to the DOM - it's really not as hard as it seems. This should also leave you in an easy place to append JSON retrieved Objects as properties to your elements!
Here's a Basic Version to give you the gist of what's happening and how to use it
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com" });
let div = create("div", { id: "myDiv" });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com", textContent: "this text is a Link in the div" });
let div = create("div", { id: "myDiv", textContent: "this text is in the div! " });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
div {
border: 3px solid black;
padding: 5px;
}
<div id="mainWrapper"></div>
Here is how to do specifically what you asked with more thoroughly notated code.
//get main wrapper
let mainWrapper = document.getElementById("mainWrapper");
//make a function to easily create elements
//function takes a tagName and an optional object for property values
//using Object.assign we can make tailored elements quickly.
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//document.appendChild is great except
//it doesn't offer easy stackability
//The reason for this is that it always returns the appended child element
//we create a function that appends from Parent to Child
//and returns the compiled element(The Parent).
//Since we are ALWAYS returning the parent(regardles of if the child is specified)
//we can recursively call this function to great effect
//(you'll see this further down)
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//these are the elements you wanted to append
//notice how easy it is to make them!
//FYI when adding classes directly to an HTMLElement
//the property to assign a value to is className -- NOT class
//this is a common mistake, so no big deal!
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
//here's where the recursion comes in:
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//keep in mind that it might be easiest to read the ac functions backwards
//the logic is this:
//Append videoImage to videoThumbnailWrapper
//Append (videoImage+videoThumbnailWrapper) to videoWrapper
//Append (videoWrapper+videoImage+videoThumbnailWrapper) to link
//Append (link+videoWrapper+videoImage+videoThumbnailWrapper) to mainWrapper
let mainWrapper = document.getElementById('mainWrapper');
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//pretty fancy.
//This is just to show the output in the log,
//feel free to just open up the developer console and look at the mainWrapper element.
console.dir(mainWrapper);
<div id="mainWrapper"></div>
Short version
Markup.js's loops.
Long version
You will find many solutions that work for this problem. But that may not be the point. The point is: is it right? And you may using the wrong tool for the problem.
I've worked with code that did similar things. I did not write it, but I had to work with it. You'll find that code like that quickly becomes very difficult to manage. You may think: "Oh, but I know what it's supposed to do. Once it's done, I won't change it."
Code falls into two categories:
Code you stop using and you therefore don't need to change.
Code you keep using and therefore that you will need to change.
So, "does it work?" is not the right question. There are many questions, but some of them are: "Will I be able to maintain this? Is it easy to read? If I change one part, does it only change the part I need to change or does it also change something else I don't mean to change?"
What I'm getting at here is that you should use a templating library. There are many for JavaScript.
In general, you should use a whole JavaScript application framework. There are three main ones nowadays:
ReactJS
Vue.js
Angular 2
For the sake of honesty, note I don't follow my own advice and still use Angular. (The original, not Angular 2.) But this is a steep learning curve. There are a lot of libraries that also include templating abilities.
But you've obviously got a whole project already set up and you want to just plug in a template into existing JavaScript code. You probably want a template language that does its thing and stays out of the way. When I started, I wanted that too. I used Markup.js . It's small, it's simple and it does what you want in this post.
https://github.com/adammark/Markup.js/
It's a first step. I think its loops feature are what you need. Start with that and work your way to a full framework in time.
Take a look at this - [underscore._template]
It is very tiny, and useful in this situation.
(https://www.npmjs.com/package/underscore.template).
const targetElement = document.querySelector('#target')
// Define your template
const template = UnderscoreTemplate(
'<a class="<%- link.className %>" href="<%- link.url %>">\
<div class="<%- wrapper.className %>">\
<div class="<%- thumbnail.className %>">\
<img src="<%- thumbnail.image %>">\
</div>\
<div class="<%- info.className %>">\
<div class="<%- info.title.className %>"><%- info.title.text %></div>\
</div>\
</div>\
</a>');
// Define values for template
const obj = {
link: {
className: 'scoreboard-video-outer-link',
url: '#someurl'
},
wrapper: {
className: 'scoreboard-video--wrapper'
},
thumbnail: {
className: 'scoreboard-video--thumbnail',
image: 'http://via.placeholder.com/350x150'
},
info: {
className: 'scoreboard-video--info',
title: {
className: 'scoreboard-video--title',
text: 'Pelicans # Bulls Postgame: E`Twaun Moore 10-8-17'
}
}
};
// Build template, and set innerHTML to output element.
targetElement.innerHTML = template(obj)
// And of course you can go into forEach loop here like
const arr = [obj, obj, obj]; // Create array from our object
arr.forEach(item => targetElement.innerHTML += template(item))
<script src="https://unpkg.com/underscore.template#0.1.7/dist/underscore.template.js"></script>
<div id="target">qq</div>