How do I check to see if a javascript object item has a key and render an element if it does exist.
This is my javascript object which is then parsed through and each item is made into a bootstrap list item. Under items.title = Groups, there is an additional key "dropdown": "true" which is the element I would like to create a dropdown menu for.
var linksNav = {
items: [
{
"type": "heading",
"title": "News",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "People",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Events",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Groups",
"href": "#",
"target": "_self",
"dropdown": "true"
},
{
"type": "heading",
"title": "Capabilities",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Initiatives",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Who we are",
"href": "#",
"target": "_blank"
},
]
}
And this is my code (that doesn't work) to try to conditionally render a dropdown (the <NavSub /> tag) if that key exists for that menu item.
The result I get is my dropdown menu for each of my list items is <div>Nothing</div>. Everything else shows up as normal, so I figure there's something wrong with my conditional statement.
render: function() {
let dropdownMenu;
if (this.props.dropdown=="true") {
dropdownMenu = (<Navsub />)
} else {
dropdownMenu = (<div>Nothing</div>)
}
return (
<li className={this.props.title + ' nav-items'}>
{this.props.title}
//I want to conditionall render this dropdown menu
<ul className="dropdown-menu fade">
{dropdownMenu}
</ul>
</li>
);
}
If "dropdown" may not be present in your props, you should use JavaScript's in operator. Also, avoid using == as it can lead to weird results due to silent type conversions.
if ('dropdown' in this.props && this.props.dropdown === 'true') {
//show dropdown
} else {
//don't show dropdown
}
The previous snippet works because JavaScript's if short-circuits.
Having said that, given that you're getting a div containing Nothing in each dropdown, it's likely your components are not receiving the props they're supposed to. You should check what props each component is getting.
This approach may be overkill for your situation where the following will work for your situation:
if ((this.props.dropdown) && this.props.dropdown === "true"){
dropdownMenu = <Navsub />;
} else {
dropdownMenu = <div>Nothing</div>
}
An alternative approach to checking whether the property dropdown exists on the object this.props, you can use Object#hasOwnProperty with the shortcut && binary operator in your condition:
// the condition will only check if dropdown is true if it exists on this.props
if (this.props.hasOwnProperty('dropdown') && this.props.dropdown === "true") {
dropdownMenu = <Navsub />;
} else {
dropdownMenu = <div>Nothing</div>
}
Object#hasOwnProperty is useful in scenarios where you want to
ensure that the property exists on the object even in the case where
its value is null or undefined.
Related
I have page, where user can create a document and one of the inputs is JsTree input, where on click opens a modal and new JsTree generates by JSON data every time. After user select some of the options, they submit it and text from selected nodes displays in input, also selected nodes themselves are saved in another hidden input.
Problem is, whenever user wants to change selected nodes, they need to open all the subnodes again, manually. I succeed only at making previously selected 1st level nodes be opened, after user selected it and opened JsTree again.
The problem is also, it takes some time for JsTree to generate subnodes, after parent node opening. So when my code opened node "2", it wants to open "2.2", but it doesn't exist at this moment. I tried to manually set setTimeout between opening every node, but it doesn't help.
My question is, how to open and select previously selected subnodes (for example 3.1.1.1) in just generated JSON JsTree
$("#container-test").on("ready.jstree", function() {
if(selectedNodes && selectedNodes.length > 0) {
selectedNodes.forEach(async function(node) {
if(node.parents.length >= 2) {
var allParents = node.parents.reverse()
for(i = 1; i < allParents.length; i++) {
nodesOpener(allParents[i])
}
}
})
}
})
function nodesOpener(parent) {
$("#container-test").jstree("open_node", $("#" + parent))
setTimeout(function() {
}, 1000)
}
The open_node method accepts a callback to call after node opened. If that doesn't work with your data fetching mechanism, maybe you should use another event.
Here's an example:
// after_open
var elem = $('#jstree_demo')
elem.jstree({
'core': {
'data': [{
"id": "ajson1",
"parent": "#",
"text": "Simple root node"
},
{
"id": "ajson2",
"parent": "#",
"text": "Root node 2"
},
{
"id": "ajson3",
"parent": "ajson2",
"text": "Child 1"
},
{
"id": "ajson4",
"parent": "ajson2",
"text": "Child 2"
},
{
"id": "ajson5",
"parent": "ajson4",
"text": "Child 2.2"
},
]
}
});
function open(id_list) {
while (id_list.length) {
var id = id_list.shift();
elem.jstree('open_node', id, function() {
open(id_list);
});
}
}
elem.on("ready.jstree", function() {
open(['ajson2', 'ajson4']);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js" integrity="sha512-TGClBy3S4qrWJtzel4qMtXsmM0Y9cap6QwRm3zo1MpVjvIURa90YYz5weeh6nvDGKZf/x3hrl1zzHW/uygftKg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/themes/default/style.min.css" integrity="sha512-pg7xGkuHzhrV2jAMJvQsTV30au1VGlnxVN4sgmG8Yv0dxGR71B21QeHGLMvYod4AaygAzz87swLEZURw7VND2A==" crossorigin="anonymous" referrerpolicy="no-referrer"/>
<div id="jstree_demo"></div>
I have the following array of words that compose some sentences:
let words = [
{
"start_time": "2.54",
"end_time": "3.28",
"alternatives": [
{
"confidence": "1.0",
"content": "Hello",
"__typename": "Alternative"
}
],
"type": "pronunciation",
"__typename": "TranscriptItems"
},
{
"start_time": null,
"end_time": null,
"alternatives": [
{
"confidence": "0.0",
"content": ".",
"__typename": "Alternative"
}
],
"type": "punctuation",
"__typename": "TranscriptItems"
},
{
"start_time": "3.29",
"end_time": "3.52",
"alternatives": [
{
"confidence": "1.0",
"content": "This",
"__typename": "Alternative"
}
],
"type": "pronunciation",
"__typename": "TranscriptItems"
}
]
Also i have this variable that contain an array of snippets selected from the words above:
const snippets = [{ start: 2.54, end: 4.00, id: 12, snippetTitle: "This is the title" }];
Right now I am showing the words by doing a simple .map and returning <span>s for every word.
What I am trying to accomplish is the following: if the span i am currently on is between a snippet from snippets variable, wrap it in another div/component so i can highlight it.
So instead of <span>Hello</span>.<span>This</span> i would be able to wrap both spans in a new wrapper component or div like <Highlight><span>Hello</span>.<span>This</span></Highlight>
You can do something like this using the reduce function:
export default myComponent = () => {
let cache = [];
return (
<>
{myArray.reduce((accumulator, currentValue) => {
if(!isInSnippets(currentValue)){
if(cache.length > 0){ // return the cached value for highlight
accumulator.push( (<Highlight>
{cache.map((elt, i) => <span key={i}>{elt}</span>)
</Highlight>));
cache = [];
}
accumulator.push(<span>{currentValue}</span>);
}
else cache.push(currentValue);
return accumulator;
}}
</>
)
}
The accumulator will contain an array of jsx elements. The regular elements will be contained in spans, while the Highlighted elements will be contained all together within a Highlight component.
We store the Highlighted elements in a cache variable. When we face a "regular" element, we check if there is something in the cache. If there is, we put them all in a highlight element, and add it to the cache. Then we add the "current" regular value.
All of the examples I've found online only show how to create single level accordion, like this:
-parent1
-child1
-parent2
-child2
I need to create dynamic accordion that has multiple nested parents, like this:
-parent
-subparent1
-subparent2
...
-subparentN
- child
My data comes in this format:
// first object in response is always considered to be the PARENT,
last one is always CHILD, and those in between are SUBPARENTS
// number of SUBPARENTS is not constant
"parents": [
{
"id": "583", // TOP LEVEL PARENT
"label": "PARENT",
"description": "irrelevant description here, i only need to show label for parents"
},
{
"id": "593",
"label": "SUBPARENT1",
"description": "..."
},
{
"id": "594",
"label": "SUBPARENT2",
"description": "..."
},
{
"id": "604",
"label": "SUBPARENT3",
"description": "..."
},
{
"id": "605", // CHILD
"label": "CHILD LABEL",
"description": "FEW LINES OF DESCRIPTION I NEED TO DISPLAY"
}
]
Based on the component you linked, it might be a good idea to have each as its own array (parent, subparent, an child label), and render its own collapsible.
For parent, have an accordion inside _renderContent function, and have that accordion be filled with its subparent. For subparent with child, have their _renderContent be filled with its child's content as well.
For parent's renderContent (see its sections props)
_renderContent = section => {
return (
<View style={styles.content}>
<Accordion
sections={SUBPARENT_SECTIONS}
activeSections={this.state.activeSections}
renderSectionTitle={this._renderSectionTitle}
renderHeader={this._renderHeader}
renderContent={this._renderContent}
onChange={this._updateSections}
/>
</View>
);
};
For subparent, do the same thing with its child label. It's probably a good idea not to have them on the same array, so you don't confuse them together.
Something weird is going on:
This is my initial state (a .js file)
import moment from 'moment';
let date = moment();
var previousDate = moment("2015-12-25");
export const projects = [
{
"id": 0,
"metadata": {
"fields":
[{
"id": 1,
"order": 1,
"name": "Name",
"value": "Collection 1"
},
{
"id": 2,
"order": 2,
"name": "Created On",
"value": date
},
{
"id": 3,
"order": 3,
"name": "Last Modified On",
"value": previousDate
},
{
"id": 4,
"order": 4,
"name": "Status",
"value": "Filed"
}],
"tags":
[{
"id": 1,
"order": 1,
"value": "tag1"
},
{
"id": 2,
"order": 2,
"value": "tag2"
},
{
"id": 3,
"order": 3,
"value": "tag3"
},
{
"id": 4,
"order": 4,
"value": "tag4"
}]
}
}
This is ProjectsList.js:
import React from 'react';
import Project from './Project';
import { projects } from 'initialState';
export default (props) => {
return(
<div className="projectsList">
{projects.map(project => (
<article key={project.id}><Project fields={project.metadata.fields} /></article>
))}
</div>
)
}
And this one's Project.js:
import React from 'react';
export default (props) => {
return(
<ul className="fields">
{props.fields.map(field => <li key={field.id}>{field.name}</li>) }
</ul>
)
}
I am trying to render a bunch of projects in a list, and every project contains a bunch of metadata key-value pairs that it shows.
So basically, the wiring does not matter, it all works fine.
Except for this:
If you look up at the initial state file (first one up there), there is an array of multiple objects in fields. Each object shows 4 key-value pairs
id
order
name
value
Now, in Project.js, the line where I go
{props.fields.map(field => <li key={field.id}>{field.name}</li>) }
looks like I can switch the {field.name} for {field.id}, to show the id in text. Or I can go {field.order}, to display the order.
But weirdly enough, if I want to show the actual value of the field, like so {field.value}, it throws.
invariant.js?4599:38
Uncaught Invariant Violation: Objects are not valid as a React child (found: Mon Jun 20 2016 21:40:33 GMT-0400). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `StatelessComponent`.
I even went as far (sigh) as changing the string value in every fields to val, juste to make sure value wasn't some kind of a reserved word.
Still threw.
Anybody can help me understand what I have done wrong, here?
Thanks Guys.
You are assigning to variable values to the value property in your state file, which are most likely not strings, but objects:
export const projects = [{
"id": 0,
"metadata": {
"fields":
[
...
{
"id": 2,
"order": 2,
"name": "Created On",
"value": date // one
},
{
"id": 3,
"order": 3,
"name": "Last Modified On",
"value": previousDate // and another one
},
...
]
...
}
}
If typeof children returns "object" (and children is neither an array, nor a ReactElement), it throws:
https://github.com/facebook/react/blob/dc6fc8cc0726458a14f0544a30514af208d0098b/src/shared/utils/traverseAllChildren.js#L169
Here's a simplest example to demonstrate this:
const IllegalComponent = () => <span>{{}}</span>
You are supposed to supply a string (or number) so that React could inline that as the children in <li>. Children should be something that's renderable and implements ReactNode.
If the children is an object, React would not know how to render it. You should explicitly convert the value to String.
Try this to see if it works:
{props.fields.map(field => <li key={field.id}>{field.value.toString()}</li>) }
I need to remove an object from an JSON tree. I know a reference to that object. Is there a nice way to do it via JavaScript or jQuery besides traversing the whole tree?
Example:
party = {
"uuid": "4D326531-3C67-4CD2-95F4-D1708CE6C7A8",
"link": {
"rel": "self",
"href": "http://localhost:8080/cim/party/4D326531-3C67-4CD2-95F4-D1708CE6C7A8"
},
"type": "PERSON",
"name": "John Doe",
"properties": {
"CONTACT": [
{
"category": "CONTACT",
"type": "EMAIL",
"key": "email",
"value": "john.doe#doe.at",
"id": "27DDFF6E-5235-46BF-A349-67BEC92D6DAD"
},
{
"category": "CONTACT",
"type": "PHONE",
"key": "mobile",
"value": "+43 999 999990 3999",
"id": "6FDAA4C6-9340-4F11-9118-F0BC514B0D77"
}
],
"CLIENT_DATA": [
{
"category": "CLIENT_DATA",
"type": "TYPE",
"key": "client_type",
"value": "private",
"id": "65697515-43A0-4D80-AE90-F13F347A6E68"
}
]
},
"links": []
}
And i have a reference: contact = party.properties.contact[1]. And I want to do something like delete contact.
You may delete it this way. I just tested it.
var party = {
// ...
}
alert(party.properties.CONTACT[0]) // object Object
delete party.properties.CONTACT[0] // true
alert(party.properties.CONTACT[0]) // undefined
Fiddle
UPDATE
In the case above party is a direct property of window object
window.hasOwnProperty('party'); // true
and that's why you can't delete a property by reference. Anyhow, behavior of delete operator with host objects is unpredictable. Though, you may create a scope around the party object and then you'll be allowed to delete it.
var _scope = {};
var _scope.party = {
// ...
};
var r = _scope.party.properties.CONTACT[0];
window.hasOwnProperty('party'); // false
alert(r) // object Object
delete r // true
alert(r) // undefined
It only works one way: a variable holds a reference, but there is no way given a particular reference to infer what variables hold it (without iterating over them and comparing).