Recursive query to specific depth - recursion

I have a tree structure which I'm trying to query to a specific depth. I'm new to relay so not sure about if I'm going about this the right way or even if its possible.
My code is currently looking like this:
class TreeRoot extends React.Component {
render() {
var container = this.props.treeRoot;
return (
<div>
<ViewNode viewNode={container.root} maxDepth={10} expand={true}/>
</div>
);
}
}
class ViewNode extends React.Component {
render() {
var vn = this.props.viewNode;
return (
<div>
<div>{vn.type} {vn.widget} {vn.mode}</div>
<ViewNodeList viewNode={vn} maxDepth={this.props.maxDepth-1}/>
</div>
);
}
}
ViewNode = Relay.createContainer(ViewNode, {
initialVariables:{
maxDepth:1,
expand:false
},
fragments: {
viewNode: (variables) => Relay.QL`
fragment on ViewNode{
id
type
widget
mode
viewNodes #include(if: $expand){
${ViewNode.getFragment("viewNode", {maxDepth:(variables.maxDepth -1),expand:(variables.maxDepth > 0)}).if(variables.expand)}
}
}`,
}
});
class ViewNodeList extends React.Component {
render() {
const vn = this.props.viewNode;
if (!vn.viewNodes){
return (<div></div>);
}
return (
<div>
{vn.viewNodes.map((el, i)=> {
return <ViewNode key={i} viewNode={el} maxDepth={this.props.maxDepth} expand={this.props.maxDepth > 0}></ViewNode>
})
}
</div>
);
};
}
TreeRoot = Relay.createContainer(TreeRoot, {
fragments: {
root: () => Relay.QL`
fragment on TreeRoot{
id
name
root{
${ViewNode.getFragment('viewNode',{maxDepth:10,expand:true})}
}
}
`,
}
}
);
The significant bit being the way I'm trying to control the recursion in the ViewNode component's viewNode fragment. It is attempting to recurse down while decrementing the 'maxDepth' variable and using the 'maxDepth' to calculate the value of the 'expand' variable. Whether to continue recursing is based on the 'expand' var.
Currently this retrieves the root and the first level of children but doesn't recurse as desired. Is what I'm trying to do possible? If it is am I on the right track or going about this in completely the wrong way?

The typical pattern is to create a fragment for the content and then nest the elements in the query. E.g.
fragment ViewContent on ViewNode {
name
}
query ViewQuery {
root {
viewNode {
...ViewContent
viewNode {
...ViewContent
viewNode {
...ViewContent
}
}
}
}
}

Related

Hide web component until browser knows what to do with it

Similar to this question:
How to prevent flickering with web components?
But different in that I can't just set the inner HTML to nothing until loaded because there is slotted content, and I don't wish to block rendering the page while it executes the web component JS.
I thought I could add CSS to hide the element, and then the init of the webcomponent unhides itself, but then that CSS snippet needs to included where ever the web component is used, which is not very modular, and prone to be forgotten
I am working on modal component, here's the code (although I don't think its particularly relevant:
<div id="BLUR" part="blur" class="display-none">
<div id="DIALOGUE" part="dialogue">
<div id="CLOSE" part="close">
X
</div>
<slot></slot>
</div>
</div>
const name = "wc-modal";
const template = document.getElementById("TEMPLATE_" + name);
class Component extends HTMLElement {
static get observedAttributes() { return ["open"]; } // prettier-ignore
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
if (this.initialised) return; // Prevent initialising twice is item is moved
this.setupEventListners();
this.init();
this._upgradeProperty("open");
this.initialised = true;
}
init() {}
get(id) {
return this.shadowRoot.getElementById(id);
}
_upgradeProperty(prop) {
/*
Setting a property before the component has loaded will result in the setter being overriden by the value. Delete the property and reinstate the setter.
https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
*/
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
// Setup Event Listeners ___________________________________________________
setupEventListners() {
this.get("CLOSE").addEventListener("click", () => this.removeAttribute("open"));
this.get("BLUR").addEventListener("click", () => this.removeAttribute("open"));
// If the dialogue does not handle click, it propagates up to the blur, and closes the modal
this.get("DIALOGUE").addEventListener("click", (event) => event.stopPropagation());
}
// Attributes _____________________________________________________________
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case "open":
// Disabled is blank string for true, null for false
if (newValue === null) this.hideModal();
else this.showModal();
}
}
// Property Getters/Setters _______________________________________________
get open() { return this.hasAttribute("open"); } // prettier-ignore
set open(value) { value ? this.setAttribute("open", "") : this.removeAttribute("open"); } // prettier-ignore
// Utils & Handlers _______________________________________________________
showModal() {
this.get("BLUR").classList.remove("display-none");
// Disable scrolling of the background
document.body.style.overflow = "hidden";
}
hideModal() {
this.get("BLUR").classList.add("display-none");
// Renable scrolling of the background
document.body.style.overflow = "unset";
}
}
window.customElements.define(name, Component);
Q: How do I hide a web component until the browser knows what to do with it?
A: Here's a solution with outside CSS. Make use of the :defined pseudo class:
class X extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).append(document.createElement('slot'));
}
}
foo.onclick = () => {
customElements.define('ab-cd', X);
foo.disabled = true;
foo.textContent = 'registered!';
}
ab-cd:not(:defined) { display: none; }
<ab-cd>text</ab-cd>
<button id="foo">click to register component</button>
I have tried to see where :defined can cause a FOUC
Only when you apply the display:none too late
<my-element>:not(:defined) { display:none }</my-element>
<style>
my-element:not(:defined) {
border: 2px solid red;
}
my-element:defined {
background: pink;
}
</style>
<style id="STYLE"></style>
<button id="BTN_STYLE">click to style component</button>
<button id="BTN_DEFINE">click to register component</button>
<script>
BTN_STYLE.onclick = () => {
STYLE.innerHTML = `my-element:not(:defined) {display:none}`;
BTN_STYLE.remove();
}
BTN_DEFINE.onclick = () => {
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).innerHTML = `constructed`;
}
connectedCallback(){
setTimeout(() => this.shadowRoot.innerHTML = `connected after 3s`,3e3);
}
});
BTN_DEFINE.remove();
}
</script>

How to update text element after property change in Polymer 3?

So I'm using a data table which has an active element. When that active elment changes I store the name of the active element in a property of my polymer element. Then I display this String property in a div.
Now I know for certain that the property change works, because I console.log it after a change, the div displaying the property doesn't update and continually displays the default value I have set.
export class ProjectsOverview extends PolymerElement {
static get template() {
return html`
...
<div>{{currentProject}}</div>
...
`
}
static get properties() {
return {
currentProject: {
type: String,
value: "placeholder",
notify: true,
reflectToAttribute: true
}
};
}
connectedCallback() {
super.connectedCallback();
const grid = this.shadowRoot.querySelector('vaadin-grid');
grid.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
grid.selectedItems = [item];
if (item) {
this.set('currentProject', item.name);
} else {
this.set('currentProject', '');
}
console.log(this.currentProject);
});
}
}
My expected result would be that every time the currentProject property is updated, the div displaying the property updates as well.
The active-item-changed callback does not have its context bound to the Polymer instance (i.e., this is the grid and not the Polymer component). Instead of the function expression, use an arrow function to automatically bind this to the correct context.
// grid.addEventListener('active-item-changed', function(event) { // DON'T DO THIS
grid.addEventListener('active-item-changed', (event) => {
/* this is the Polymer instance here */
this.set('currentProject', ...);
})
Your scope is wrong. You're using an anonymous function so when you try to set currentProject, you do that when your this is your anonymous function. Use .bind(this) to fix your problem.
grid.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
grid.selectedItems = [item];
if (item) {
this.set('currentProject', item.name);
} else {
this.set('currentProject', '');
}
console.log(this.currentProject);
}.bind(this));

How to style each element of ngFor individually based on its value?

I have a console on the screen that I log things to. The component looks like this.
export class AppComponent { logs: string[]; ... }
<div *ngFor="let log of logs" class="log-type-a">{{log}}</div>
I'd like to set a class on each of the DIVs dynamically. At the moment, each of those is log-type-a but I'd prefer it to be log-type-b when the string contains a certain value (e.g. it starts with "b", so that the class would be log-type-first_char_of_log.
I'm not sure what to google for to being with. I've tried horsing around with ngClass but failed due to ignorance and uncertainty on how.
NgFor can have an index, like this one: *ngFor="let item of items; index as i;"
You could use that index in order to set different classes for your items, like class="log-type-{{ i }}".
https://angular.io/api/common/NgForOf
You can use expresion in ngClass like this:
export class AppComponent { logs: string[]; ... }
<div *ngFor="let log of logs" [ngClass]="'log-type-' + log">{{log}}</div>
If your logs array has lets say: red and blue
the class output should be:
log-type-red
log-type-blue
OR
you can use functions like this and make decisions depending on the log value:
<div *ngFor="let log of logs" [ngClass]="'class-test-' + ngClassConvert (log)">
My log value: {{ log }}
Converted in cssClass: {{ ngClassConvert (log)}}
</div>
and the component:
export class AppComponent implements OnInit {
name = 'Angular';
logs: string[];
ngClassConvert(value):string{
let returnValue = '';
switch (value){
case 'a': {
returnValue = 'Apple';
break;
}
case 'b': {
returnValue = 'Banana';
break;
}
case 'c': {
returnValue = 'Cherry';
break;
}
case 'd': {
returnValue = 'dddd';
break;
}
default: {
returnValue = 'default';
break;
}
}
return returnValue;
}
ngOnInit(): void {
this.logs = ['a', 'b', 'c', 'd'];
}
}
Also and Demo
Based on the answer of SehaxX: You could parse the list of logs using a setter. This will save you function calls in the template.
private _logs: string[];
public parsedLogs: any[];
set logs(value: string[]) {
this._logs = value;
this.parsedLogs = this.parseLogs(value);
}
private parseLogs(logs: string[]): any[] {
let parsed = [];
for (let i = 0; i < logs.length; i++){
parsed.push({
value: logs[i],
style: this.ngClassConvert(logs[i])
});
}
return parsed;
}
Demo

render() method is not correctly called after props update

I'm doing a very simple react+redux application where I've a reducer called goals and a container called GoalsContainer.
From App Container I call the action goal for load the initial goals from a local db(indexedDB)
dispatch(loadGoals(currentDate));
This call the loadGoals from the goals actions:
export function loadGoals(currentDate = new Date()){
return dispatch => {
var goals = getGoalsFromDB(normalizeDate(currentDate)); // with this I get an array from the db
dispatch(setLoadGoals(goals));
}
}
function setLoadGoals(goals) {
return {
type: types.LOAD_GOALS,
goals
};
}
And then in my reducer I've this:
export default function goals(state = [], action) {
switch(action.type) {
case types.LOAD_GOALS:
return action.goals; // here I set the state of the goal reducer with the array passed via action
default:
console.log('Im here');
return state;
}
}
and this is my GoalsContainer(read the comments in code):
class GoalsContainer extends React.Component {
render() {
if (this.props.goals != undefined) {
console.log('ok called the render'); // in chrome console shows it
console.log(this.props.goals); // in chrome console shows correctly the goals loaded
console.log(this.props.goals.length); // it say 2
if (this.props.goals.length > 0) { // here fails...
console.log('good');
console.log(this.props.goals);
var goalsView = <div>There are goals</div>
}
else {
console.log('why go here?'); // go here
console.log(this.props.goals);
var goalsView = <div>No goals</div>
}
} else {
var goalsView = <div>Undefined</div>
}
return (
<div id="goals-main">
{goalsView}
</div>
);
}
}
GoalsContainer.propTypes = propTypes;
function mapStateToProps(state) {
const { goals, environment } = state;
const { currentDate } = environment;
return {
goals,
currentDate
}
}
export default connect(mapStateToProps)(GoalsContainer);
The problem is that when it does the if check, it fails(like if there are 0 goals), but in chrome console show correctly the goals array...
Then if I force with some workaround the render(), all works correctly.
What I've done wrong ?
You didn't mention if you use https://github.com/gaearon/redux-thunk or not. To use reducer returning function you should definitely install it.
It's hard to follow all of the parts of your code from random gists. What happens if you change your GoalsContainer to be;
class GoalsContainer extends React.Component {
render() {
console.log(this.props.goals);
return (
<div id="goals-main">
{(this.props.goals.length >= 1)?<div>There are goals</div>:<div>Nope!</div>}
</div>
);
}
}
What gets logged to the console?

Performance concern with a React.js looping picture slideshow

We want to display a looping slideshow of pictures that looks like a gif. The current result is visible at this url: https://figuredevices.com.
Our current approach is using opacity to show or hide slides:
class SlideShow extends React.Component {
constructor(props, context) {
super(props);
this.state = {
currentSlide: 0
};
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(this.transitionToNextSlide.bind(this), 200);
}
componentWillUnmount(){
if(this.interval){
clearInterval(this.interval);
}
}
transitionToNextSlide() {
let nextSlide = this.state.currentSlide + 1;
if (nextSlide == this.props.slides.length) {
nextSlide = 0;
}
this.setState({currentSlide: nextSlide});
}
render () {
let slides = this.props.pictures.map((picture, idx) => {
let slideContainerStyle = {
opacity: this.state.currentSlide == idx ? 1 : 0
};
return(
<div style={slideContainerStyle} key={idx}>
<Slide picture={picture}/>
</div>
);
})
let containerStyle = {
width:'100%'
};
return (
<div style={containerStyle}>
{slides}
</div>
);
}
};
Pictures are loaded 5 by five into this.props.picture. The number of pictures is not bounded and I am worried about performance as this number grows. There are two things that don't feel right to me:
The map operation in the render method is traversing a whole array every 200ms only to change two css properties.
The DOM is growing a lot in size but most of nodes are hidden
Would you suggest a better approach, maybe using animation or react-motion ?
You should maintain all of your pictures as an array, and every 200ms, increment the array index. Then, instead of displaying all of the pictures every single time, just have it display the picture at your current index. This is a lot better because you're only returning one photo ever instead of a bunch of invisible ones :)
Note: I wasn't able to test this, so I'm not sure if everything is exactly syntactically correct, but this is the general idea. Every 200ms, increment the slideIndex. Then, always return a div with only the one picture you want to see.
class SlideShow extends React.Component {
constructor(props, context) {
super(props);
this.state = {
slideIndex;
};
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(this.transitionToNextSlide.bind(this), 200);
}
componentWillUnmount(){
if(this.interval){
clearInterval(this.interval);
}
}
transitionToNextSlide() {
this.setState({this.state.slideIndex: (this.state.slideIndex + 1) % this.props.slides.length})
render () {
let containerStyle = {
width:'100%'
};
return (
<div style={containerStyle}>
<Slide picture={this.props.pictures[this.state.currentSlide]}/>
</div>
);
}
};

Resources