Reactjs append an element instead of replacing - javascript

I'm trying to iterate through a list/array/object of things: (I used coffeescript to keep it clear, jsfiddle of full JS here. but it's just a forEach)
pages = for page, each of #props.ids
$('#start').append("<div id='"+page+"' ></div>")
React.renderComponent page(title: each.title, text: each.text), $("#"+page)[0]
and append each of them, instead of replacing, leaving only the last item in the list.
Where the #start element is the starting container, and I want to populate it with multiple elements, but I need to give each their own container, otherwise they will all overwrite eachother, the default reactjs behaviour.
I'm wondering if there's a way to tell react to append instead of replacing the div.
I'd like to avoid using jquery if possible, and do it purely react-ly.
I though about giving the React.renderComponent page the initial list, and then iterate in the previously called template, however, then i'm facing a different problem, I have to return a reactjs element object, consisting of the whole list, which I really don't prefer.
I need for the initial call to create individual, independent react templates, appending eachother in a list, prefferably without any extra containers, or using jquery.

I think you're getting the concept wrong. React's renderComponent indeed renders a single component, somewhere. Doing this multiple times only re-renders the same component at that place (aka idempotent). There's no real "append" statement, at least not in the way you asked for.
Here's an example of what you're trying to achieve. Forgot about renderComponent in this. It's just to put the component somewhere.
/** #jsx React.DOM */
var pages = [{title: 'a', text: 'hello'}, {title: 'b', text: 'world'}];
var App = React.createClass({
render: function() {
return (
<div>
{
this.props.pages.map(function(page) {
return <div>Title: {page.title}. Text: {page.text}</div>;
})
}
</div>
);
}
});
React.renderComponent(<App pages={pages} />, whateverDOMNodeYouWantItToBeOn);
See what I did there? If I want multiple divs, I just create as many as I want to see. They represent the final look of your app, so making a same div be "appended" multiple times doesn't really make sense here.

Create a div with a class extradiv:
<div class="extradiv">
</div>
In CSS, Set It's display to none:
.extradiv {
display: none;
}
.extradiv * {
display: none;
}
In JS implement this function:
function GoodRender(thing, place) {
let extradiv = document.getElementsByClassName('extradiv')[0];
ReactDOM.render(thing, extradiv);
extradiv = document.getElementsByClassName('extradiv')[0];
place.innerHTML += extradiv.innerHTML;
}
Than you can use this in place of ReactDOM.render:
GoodRender(<Component>My Text</Component>, YourDOMObject)

Related

Altering the DOM manually in React

I am using Ionic's IonPicker component, which renders, as a sibling to React's div#root, an ion-picker element, which appears to be a full screen overlay, which has two children: an ion-backdrop element, and its sibling, div.picker-wrapper.
I am trying to extract just the div.picker-wrapper element so that I can, say, place it relative to other components in my app, and I also want to be able to add elements to it, like a button that is a child of div.picker-wrapper, and that button would have to be a react component. So I suppose individual access to div.picker-wrapper's existing children, div.picker-toolbar and div.picker-columns, would be good as well.
And of course I need to be able to use the IonPicker's props and methods and everything.
So I'm trying to grab DOM elements and delete them, move them around, stuff like that, and of course I don't ever want the user to see things disappearing and reappearing, so, there's that.
I imagine the answer involves "Use refs" so please provide more detail. To start, I've tried
picker: RefObject<HTMLIonPickerElement> = React.createRef();
render() {
return (
<IonPicker
isOpen
ref={ this.picker }
animated={ true }
mode={ 'ios' }
cssClass={ 'firmm-picker' }
columns={ this.columns }
onDidDismiss={ this.props.dismiss }
buttons={ this.buttons }
showBackdrop={ false }
/>
);
}
But picker and/or picker.current are always undefined or null.
And I've tried const x = document.getElementById('ion-overlay-1') in various places (componentDidMount, render, shouldComponentUpdate), and it's never there.

How do we add custom data-attributes to Gutenburg's editor.BlockListBlock?

For Gutenburg's "core/image" block, they have different alignment options, specifically two that expose "full" and "wide". When you click on either one of these options, you will see that the data attribute "data-align" is added to the block editor's wrapper component with a value of either "full" or "wide".
I'm trying to create a custom block that has a similar features as the above. But I'm having a hard time figuring out how to add that custom attribute to my component's block editor wrapper.
Some things that I've tried are:
Using the block filter editor.BlockListBlock but the most I could do with this with my own knowledge is adjust the props, and the className. Adding a data-align="full" just meant adding a prop called data-alignment here.
https://developer.wordpress.org/block-editor/developers/filters/block-filters/#editor-blocklistblock
I also tried doing this with jQuery. Even if this worked, I definitely didn't want to use it as a permanent solution, I just wanted to see if it would work. So I added an on('click') event to one of my buttons so that it would target the wrapper component and modify the node, but that didn't work either. Probably because the block element is a dynamic element, it couldn't even be selected.
This is the wrapper that I'm trying to add a custom attribute to,
<div id="block-388288fa-ff20-459e-bce7-543b94fd77c4" class="wp-block editor-block-list__block block-editor-block-list__block is-selected" data-type="cgb/block-ee-hero-slider" tabindex="0" aria-label="Block: Hero Slider">
I just ran into the same problem. I found two solutions.
With getEditWrapperProps()
If you define your block yourself through registerBlockType(), then you can use getEditWrapperProps to define the data-align attribute:
registerBlockType('my-fully-aligned-block', {
title: 'My Fully Aligned Block',
category: 'common',
icon: 'admin-appearance',
/**
* Sets alignment.
*
* #param attributes
* #returns {{'data-align': *}}
*/
getEditWrapperProps(attributes) {
return {
'data-align': 'full'
};
},
edit,
save: () => null
});
With the editor.BlockListBlock filter
If you want to change the alignment for an existing block, you can use the editor.BlockListBlock filter that you already tried. Instead of setting the className property, like in the example in the documentation, you can pass in wrapperProps, that is going to be merged with what is defined in getEditWrapperProps().
function FullAlign(BlockListBlock) {
return props => {
const { block } = props;
// Bail out if it’s not the block we want to target.
if ('cgb/block-ee-hero-slider' !== block.name) {
return <BlockListBlock {...props} />;
}
return (
<BlockListBlock {...props} wrapperProps={{ 'data-align': 'full' }} />
);
};
}
wp.hooks.addFilter(
'editor.BlockListBlock',
'cgb/block-ee-hero-slider',
FullAlign
);

Swapping BODY content while keeping state

Dynamically swapping BODY content using jQuery html function works as expected with 'static' content.
But if forms are being used, current state of inputs is lost.
The jQuery detach function, which should keep page state, seems to be blanking out the whole page.
The following code is the initial idea using jQuery html but of course the text input value will always empty.
function swap1( ) {
$("body").html('<button onclick="swap2();">SWAP2</button><input type="text" placeholder="swap2"/>');
}
function swap2( ) {
$("body").html('<button onclick="swap1();">SWAP1</button><input type="text" placeholder="swap1"/>');
}
With not knowing what form inputs there are, how would one swap in and out these forms in the BODY and keep track of the form states?
Demo of two text inputs which should keep state when they come back into the BODY:
https://jsfiddle.net/g7hksfne/3/
Edit: missunderstood use case. This covers saving the state while manipulating the DOM, instead of switching between different states.
Instead of replacing the <body>'s content by serializing and parsing HTML, learn how to use the DOM. Only replace the parts of the DOM you actually change, so the rest of it can keep its state (which is stored in the DOM).
In your case: you might want to use something like this:
function swap1( ) {
document.querySelector("button").onclick = swap2;
document.querySelector("button").textContent = "SWAP2";
document.querySelector("input").placeholder = "swap2";
}
function swap2( ) {
document.querySelector("button").onclick = swap1;
document.querySelector("button").textContent = "SWAP1";
document.querySelector("input").placeholder = "swap1";
}
<button onclick="swap1();">SWAP1</button><input type="text" placeholder="swap1"/>
(This is not optimized and should only serve as an example.)
Put the content you want to save in a node below <body>, like a simple ´` if you don't already have a container. When you want to save and replace the container, use something like:
var saved_container = document.body.removeChild(document.querySelector("#app_container");
// or document.getElementById/getElementsByClassName, depends on container
The element is now detached and you can add your secondary to document.body. When you want to get back, save the secondary content (without overwriting the first container of course), then reattach the primary content it with:
document.body.appendChild(savedContainer);

Polymer, evaluate element based off object

I am using the tile example from polymers neon elements - and I am trying to make each expanded tile unique. My first try on how to do this was to pass a string in with the grid items like
{
value: 1,
color: 'blue',
template: 'slide-1'
}
And have that element be evaluated when rendered in a new element something like this. (this is the card template itself)
<template>
<div id="fixed" class$="[[_computeFixedBackgroundClass(color)]]"></div>
<div id="card" class$="[[_computeCardClass(color)]]">
<[[item.template]]></[[item.template]]>
</div>
This does not work - however I am wondering if there is some way to do this so I can load custom elements for the content of each card. For reference -https://elements.polymer-project.org/elements/neon-animation?view=demo:demo/index.html&active=neon-animated-pages , it is the grid example and I am trying to replace the content of each card once it is clicked on ( the fullsize-page-with-card.html, here is all the html for it - https://github.com/PolymerElements/neon-animation/tree/master/demo/grid ). Is this the wrong way of approaching this? Or maybe I have some syntax wrong here. Thanks!
Edit : OK, So I can send it through if i add it to the click to open the card like so
scope._onTileClick = function(event) {
this.$['fullsize-card'].color = event.detail.data.color;
this.$['fullsize-card'].template = event.detail.data.template;
this.$.pages.selected = 1;
};
and in the card's properties like so
template: {
type: String
},
So I can then evaluate it as [[template]] , however - the question still remains how to call a custom element (dynamically) using this string. I could pass a couple of properties and fill in a card or form so they are unique, but i think I would have much more creative freedom if I could call custom elements inside each card.
I have an element that allows referenced templates. There are a couple of others other there, but this one also allows data bindings to work: https://github.com/Trakkasure/dom-bindref

How to add dynamic parameters/variables to templates rendered with ng-bind-html or custom directive (inside ng-repeat)

EDIT: Here is the solution sample based on the first answer. You're still welcome to offer further suggestions and fill some gaps in my knowledge (see comments).
plnkr.co/edit/mqGCaBHptlbafR0Ue17j?p=preview
ORIGINAL POST
Hello all Angular heads,
I’m trying to build a tool for building simple web pages from a set of predefined elements. The end user designs the layout by choosing which elements appear in which order (there would be a selection of maybe 20-30 different elements). An example layout:
Heading
Paragraph
Paragraph
Subheading
Barchart
Paragraph
…and so forth
Under the hood, there would be an array of Javascript objects:
var page_structure = [
{type: ”heading”, content: ”The final exam - details” },
{type: ”paragraph”, content: ”Bla bla bla bla bla…” },
{type: ”paragraph”, content: ”Bla bla bla bla bla…” }
…
{type: ”bar chart”, y: ”Grades”, colour: ”blue” }
…
];
The end user inputs parameters like content, variable, colour, etc, for each individual element.
THE PROBLEM
I can’t figure how to draw these elements on the page so that the dynamic parameters for each element are properly included.
Each element type (heading, paragraph, bar chart, other possible elements) has it’s own HTML template, which has to be able to dynamically display user-defined parameters. I’m thinking I need something like this:
var templates = {
heading: ”<h1>USER_CHOSEN_PARAMETER_HERE</h1>”,
paragraph: ”<p>USER_CHOSEN_PARAMETER_HERE</p>”
…
};
I’m using Angular’s ng-repeat directive to draw each element. I have tried using either ng-html-bind…
<div ng-repeat="x in page_structure track by $index">
<div ng-bind-html="templates[x.type]"></div>
</div>
…or a custom directive, which I call mycomponent:
<div ng-repeat="x in page_structure track by $index">
<div mycomponent component='x'></div>
</div>
Both methods work just dandy when there are no user defined parameters in templates. I can’t, however, wire up the parameters. With ng-bind-html, I have tried using an expression markup like this:
var templates = {
heading: ”<h1>{{x.content}}</h1>”,
paragraph: ”<p>{{x.content}}</p>”,
…
};
(I actually define templates in app controller constructor, so it’s $scope.templates = { bla bla } to be precise.)
This is just showing curlies and ”x.content” in the actual web page. How do I refer to a dynamically variable parameter inside ng-html-bind template, or is it even possible?
I also tried the custom directive route, defining the directive as
.directive('mycomponent', function() {
return {
scope: { component: "=" },
template: templates[component.type]
}; )
This was even more messed up, since I actually couldn’t figure out how I should even try to refer to a dynamic parameter here inside the template. So I apologise my inability to offer a meaningful example of what I tried to do.
Any help or working examples of the methods I should use here are greatly appreciated. I’m happy to provide more details if needed (this is my first post in SO, so I’m still trying to get the jist of how to flesh out the questions).
I suggest using ngInclude, in which you can have a funciton that check for the proper template to load based on the passed page structure.
<div ng-repeat="structure in page_structure">
<div ng-include="getMyTemplate(structure)"></div>
</div>
$scope.getMyTemplate = function(structure) {
switch (structure.type) {
case 'heading':
return '/Views/Heading.html' // or id of a pre-loaded template
case 'paragraph':
return '/Views/Paragraph.html' // or id of a pre-loaded template
}
}

Categories

Resources