React ES6 class method does not render into html table but outside it. Why? - reactjs

I want to understand ES6 and are using HTML table to render external React elements. Having trouble rendering this ES6 class into a html table.
I've tried rendering ES6 object from inside the class by returning a string to a HTML table which didn't render so now I render it externally to the class which renders to the bottom outside the table. I am using external CSS for the table and none of the other rendered elements are affected and are rendering to thier cells.
React.js
//PROBLEM: To create an igor skate from an inline skate model.
//START
//CREATE Class Roller_Blades.
//CONSTRTUCT property Name "Inline Skate"
//RETURN SkateType "I am a " Name " designed for mass production."
//END CLASS
class InlineSkate {
constructor(name) {
this.brand = name;
}
present() {
return "I am a " + this.brand + " designed for mass production."
}
}
//This string outside the class will output 'K2 Skate' back into
// InlineSkate.CreateRollerBlade().
var skate = new InlineSkate('K2 Skate');
document.write(skate.present());
ReactDOM.render(React.createElement(InlineSkate, null), document.getElementById('esSixClassExample'));
HTML
<body>
<!--Output-->
<table ID="DoubleBorderedTable">
<tbody>
<tr>
<th>ES6 Class Example</th>
</tr>
<tr>
<td><div id="hello-example"></div></td>
<td><div id="clock-example"></div></td>
<td><div id="es6ClassExample"></div></td>
</tr>
</tbody>
</table>
<!-- Load React. -->
<!-- Note: when deploying, replace "development.js" with
production.min.js". -->
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<!-- Load our React components.-->
<script src="scripts/simple_component.js"></script>
<script src="scripts/Clock.js"></script>
<script src="scripts/ESSixClassExample.js"></script>
</body>
The class function "present" that returns a string ends up rendering outside and below the table and not in the desired last cell of the last row of the table.

ReactDOM.render(React.createElement(InlineSkate, null), document.getElementById('esSixClassExample'))
This is doing nothing. I'd be willing to bet that if you opened your console you'd see an error that says something like, "Cannot call a class as a function".
What's printing the string outside the table are these two lines,
var skate = new InlineSkate('K2 Skate');
document.write(skate.present());
As it is you aren't really using React at all. The first argument of ReactDOM.render() should be either a function component, a class component, or a string designating the type of element to be created.
In order to get the output you want without changing what you're doing so much, you'd need to do something like this,
var skate = new InlineSkate("K2 Skate");
ReactDOM.render(
React.createElement("span", { children: skate.present() }),
document.getElementById("esSixClassExample")
);
What that will do is create a span element, stick it inside your esSixClassExample div, and then run your present function to generate your text and assigning the text node as a child of the span.
This is pretty irregular though, and unless you're just doing this for learning purposes or as part of a class or something, you really ought to be using React components (because why even bother with React otherwise.)
Here is a class component example.
class InlineSkate extends React.Component {
render() {
return React.createElement(React.Fragment, null, "I am a ", this.props.brand, " designed for mass production.");
}
}
ReactDOM.render(React.createElement(InlineSkate, {
brand: "K2 Skate"
}), document.getElementById('classComponent'));
And here is a function component example.
function InlineSkate({ brand }) {
return React.createElement(React.Fragment, null, "I am a ", brand, " designed for mass production.");
}
ReactDOM.render(React.createElement(InlineSkate, {
brand: "K2 Skate"
}), document.getElementById('functionComponent'));

Related

Procedurally generate html elements with Preact and HTM

I'm using Preact with HTM (no compiler required) and am having trouble looping through an object and creating a DOM element for each item.
What is the correct way to procedurally generate DOM elements with Preact + HTM?
import { h, Component, render } from 'https://unpkg.com/preact?module';
import htm from 'https://unpkg.com/htm?module';
const html = htm.bind(h);
function componentValues() {
var elements = {e1:10, e2:20};
var objEditor = '<div class="row">';
for (const key in elements) {
objEditor += '<div class="col">'+key+'</div>';
}
objEditor += '</div>';
return objEditor;
}
function renderPage() {
render(html`
<div class="container-xl">
<p>Hello World</p>
<${componentValues} />
</div>`, document.getElementById("app"));
}
renderPage();
My result is this
Hello World
<div class="row"><div class="col">e1</div><div class="col">e2</div></div>
https://codepen.io/28raining/pen/WNyaJrL
Are you looking to use HTML strings in JSX or simply find the most idiomatic way to go about generating new entries?
If this is the latter (as your description seems to indicate), you definitely want to avoid using strings. You have JSX at your disposal; use it to template out your content rather than string concatenation.
Try this:
function componentValues() {
var elements = {e1:10, e2:20};
return html`
<div class="row">
${Object.entries(elements).map(([key, value]) =>
html`<div class="col">${key}</div>`
)}
</div>
`;
}
If you did need to use HTML strings (it happens, though should be avoided if at all possible), that string isn't a JSX element/component, so you can't use it as one. Instead, you need to wrap it with html:
function renderPage() {
render(html`
<div class="container-xl">
<p>Hello World</p>
${html([componentValues()])}
</div>`, document.getElementById("app"));
}
Tagged template functions (html in this case) get arguments as arrays, hence why we create a new array and add the result of componentValues() to it.
As rschristian proposed, the solution is with arrays.
rschristian's solution is limited by what you're allowed to put in a template string.
function compRow() {
return html`<div class="row">${componentValues()}</div>`
}
//Boxes for users to add components
function componentValues() {
var elements = state.elements;
var objEditor = []
for (const key in elements) {
objEditor.push(html`<div class="col">${key}</div>`);
}
return objEditor;
}

How do I prevent a Phoenix Live View from removing DOM elements put there by JavaScript?

I haven't yet found a way to consistently prevent Phoenix Live Views from removing elements from the DOM that were put there by JavaScript. Here is a snippet from a crude chat app that uses JavaScript to add a smiley face after the page loads:
<span><%= live_patch "New Chat", to: Routes.chat_index_path(#socket, :new) %></span>
<div id="smiley" style="font-size: 80px" phx-update="ignore"></div>
<script>
$(document).ready(function() {
$('#smiley').html('😃')
});
</script>
The phx-update="ignore" attribute prevents the smiley face from disappearing right away, but adding a new chat causes it to disappear:
You can prevent it from disappearing after New Chat by using phx-hook like this:
<div id="smiley" style="font-size: 80px" phx-hook="Smiley"></div>
and in app.js:
let Hooks = {}
Hooks.Smiley = {
mounted() {
this.el.innerHTML = '😃'
}
}
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: Hooks})
The complete code is here: https://github.com/ijt/rantclub/tree/c383530ce9749beb21f2c60605576f0d047043f8.
One caveat: for some reason this does not prevent Delete from removing the smiley face.

Unable to access merge field values inside visualforce component in angularJs

View:
<div ng-controller = "ClPortalRegistrationController">
<div ng-repeat="(key, value) in ObjectApiFieldsetMap">
{{key}} {{value}} //this is printing correct result
<c:FieldSetComponent objectAPIName="'{{key}}'" fieldSet="'{{value}}'" cid="'{{key}}'"
sectionTitle="Section 1" columns="2" textAlign="center"></c:FieldSetComponent>
</div>
</div>
Controller:
$scope.ObjectApiFieldsetMap = {
Applications__c: "Application_FieldSet_One",
clcommon__Collateral__c: "Collateral_FieldSet_One"
};
Now when I'm trying to access {{key}},{{value}} inside c:FieldSetComponent ,its only passing string as {{key}} and {{value}} and not the converted result. How can I access values stored inside key, value inside component?
Posting the solution which I implemented as a work around.
Turns out that you cannot access angular merge field values inside visualforce components. So instead of manipulating(segregating input into key-value pair) values inside angular controller, I have to push the logic to apex controller.
<apex:component controller="RegistrationController" access="global">
<apex:repeat value="{!ObjectApiFieldsetMap}" var="apiName">
<c:FieldSetComponent objectAPIName="{!apiName}" fieldSet="{!ObjectApiFieldsetMap[apiName]}"
cid="{!apiName}{!ObjectApiFieldsetMap[apiName]}"
columns="1" textAlign="center">
</c:FieldSetComponent>
</apex:repeat>
</apex:component>
And in my apex controller i.e RegistrationController , I have set the logic to segregate key values from a map input which I'm using inside visualforce component
global class RegistrationController {
global Map<String,String> ObjectApiFieldsetMap {
get {
ObjectApiFieldsetMap = arrangeApiFieldSetsByOrder();
return ObjectApiFieldsetMap;
}
set;
}
global Map<String,String> arrangeApiFieldSetsByOrder() {
Map<String,String> ObjectApiFieldsetMap = new Map<String,String>();
/* logic for segregation */
return ObjectApiFieldsetMap;
}
}

Changing the template data not refreshing the elements

I have searched and tried suggestions mentioned in various posts but no luck so far.
Here is my issue.
I have created a custom element <month-view id="month-view-element"></month-view> in my mainpage.html. Inside mainpage.html when this page is initially loaded i created a empty json object for all the 30days of a month and print a placeholder type cards in UI. Using the code below.
var json = [];
for(var x = 0; x < total; x++) {
json.push({'hours': 0, 'day': x+1, 'year': year});
}
monthView.month = json; //Doing this line. Prints out the desired empty cards for me in the UI.
created a month-view.html something like below:
<dom-module id='month-view'>
<template>
<template is="dom-repeat" items= "{{month}}">
<paper-card class="day-paper-card" heading={{item.day}}>
<div class="card-content work">{{item.work}}</div>
<div class="card-actions containerDay layout horizontal">
<div style="display:inline-block" class="icon">
<paper-icon-button icon="icons:done" data-hours = "8" data-day$="{{item.day}}" data-month$={{item.month}} data-year$={{item.year}} on-click="updateWorkHours"></paper-icon-button>
<paper-tooltip>Full day</paper-tooltip>
</div>
</div>
</paper-card>
</template>
</template>
<script>
Polymer({
is: "month-view",
updateWorkHours: function (e, detail) {
console.log(e);
this.fire('updateWorkHour', {day: e.target.dataHost.dataset.day,
month: e.target.dataHost.dataset.month,
year: e.target.dataHost.dataset.year,
hours: e.target.dataHost.dataset.work
});
}
});
</script>
</dom-module>
There is another file script.js which contains the function document.addEventListener('updateWorkHour', function (e) { // doStuff });. I use this function to make a call to a google client API. I created a client request and then do request.execute(handleCallback);
Once this call is passed i landed in handleCallback function. In this function i do some processing of the response data and save parts of data into json variable available in the file already. And once all processing is done i did something like below.
monthView.month = json;
But this above line is not refreshing my UI with the latest data. Is there anything I am missing? Any suggestions or anything i am doing incorrectly.
You need to use 'set' or 'notifyPath' while changing Polymer Object or Arrays in javascript for the databinding/obserers to work. You can read more about it in https://www.polymer-project.org/1.0/docs/devguide/data-binding.html#path-binding
In your case try below code
monthView.set('month',json);
Updated suggestions:
Wrap your script on main page with. This is required for non-chrome browsers.
addEventListener('WebComponentsReady', function() {})
This could be scoping issue. Try executing 'document.querySelector('#month-view-element');' inside your callback addWorkHoursCallBack. Also, Use .notifyPath instead of .set.

Composite C1 - Using MVC RenderSections or the like

In Composite C1 I'm using Razor syntax to create my master layout. For faster loadtimes it's recommended to put your scripts just before the end body tag instead of inside the head tag. That's why I put jQuery and other scripts just before the end body tag.
When I use a Razor function with JavaScript that refers to jQuery I get an error because jQuery hasn't been loaded yet. The HTML from the Razor function is output before the jQuery script is loaded:
Uncaught ReferenceError: $ is not defined
In MVC I can use RenderSection in the master layout to accomplish this (rendering the JavaScript below my master layout scripts
#RenderSection("FooterScripts", false)
Then in my views I can define a section like this:
#section FooterScripts {
<script type="text/javaScript">
$(function () {
...
});
</script>
}
Which will render the HTML in the correct place in the final HTML. Is this possible to do in Composite C1? I couldn't get RenderSection to work even though Intellisence tells me it's available.
There's no built in way to insert html markup from a C1 function to a specific place in a layout.
Possible ways to implement your own logic would be:
Collect the scripts to be insterted in f.e. Context.Items collection, and insert them in the end.
Implement some post processing logic that would move the script tags to the bottom of the page after it is rendered.
First way is easier to implement, here's a short working example:
C1 Function code:
#inherits RazorFunction
#using Composite.Examples
#functions {
}
#{
LayoutHelper.AddDelayedScript(Script());
}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
Inserting a script at the bottom of a page
</body>
</html>
#helper Script() {
<script type="text/javascript">
alert("I'm inserted!");
</script>
}
Layout code:
....
#LayoutHelper.RenderDelayedScripts()
</body>
</html>
Class LayoutHelper, defined in App_Code:
using System.Collections.Generic;
using System.Text;
using System.Web;
namespace Composite.Examples
{
public static class LayoutHelper
{
private const string HttpItems_Key = "delayedscripts";
public static void AddDelayedScript(IHtmlString script)
{
var context = HttpContext.Current;
lock (context.Items.SyncRoot)
{
if (!context.Items.Contains(HttpItems_Key))
{
context.Items.Add(HttpItems_Key, new List<IHtmlString>());
}
(context.Items[HttpItems_Key] as List<IHtmlString>).Add(script);
}
}
public static IHtmlString RenderDelayedScripts()
{
var context = HttpContext.Current;
var sb = new StringBuilder();
if (context.Items.Contains(HttpItems_Key))
{
foreach (var delayedscript in context.Items[HttpItems_Key] as IEnumerable<IHtmlString>)
{
sb.Append(delayedscript.ToHtmlString());
}
}
return new HtmlString(sb.ToString());
}
}
}

Resources