I am working on a fairly complex web app using React.js, and I would like to allow the app's users to use one of the components as an API widget on their sites as well with a script tag (think Google Maps API, Analytics, etc.)
I'm new to React, but I think React takes all of the components, etc. in the app, and bundles them into a single JS file.
Is it possible to bundle only one component into a JS file, then let users place that file in their site, and use it as a widget?
I've tried transpiling the component I want users to use as a widget with reactify, but I can't seem to get it to work.
Any thoughts?
Absolutely, however they will still need to include React as a dependency, or you would have to in your widget.
To use React you don't need to use transpilation, i.e. you don't need reactify. Building a widget for React is like building a widget for jQuery.
// Your widget root component
var SayHelloComponent = React.createClass({
render() {
// You use React.createElement API instead of JSX
return React.createElement('span', null, "Hello " + this.props.name + "!");
}
});
// This is a function so that it can be evaluated after document load
var getWidgetNodes = function () {
return document.querySelectorAll('[data-say-hello]');
};
function initializeWidget(node) {
// get the widget properties from the node attributes
var name = node.getAttribute('data-say-hello');
// create an instance of the widget using the node attributes
// as properties
var element = React.createElement(SayHelloComponent, { name: name });
ReactDOM.render(element, node);
}
getWidgetNodes().forEach(initializeWidget);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!-- What it would look to include your widget in a page -->
<div data-say-hello="Guzart"></div>
The only reason you would need to transpile your code would be to use JSX and ES6.
Related
I have to send some emails and I simply want to re-use as much code/knowledge as possible (just because), for this I want to render a React component to raw HTML with inline classes.
I have managed to render a React component to static markup via:
const TestMail = () => {
return (
<div>
<h1 className="text-xl font-bold border-b">You have a new Test Email on Productlane</h1>
<p className="border-b">Something something</p>
<a href="https://productlane.io/feedback" className="bg-purple-600">
Open
</a>
</div>
)
}
export function testMailer({ to }: IParams) {
const emailHtml = ReactDOMServer.renderToStaticMarkup(<TestMail />)
const processedHtml = juice(emailHtml, {
webResources: {
// relativeTo: "app/core/styles/index.css",
},
})
return {
async send() {
console.warn("trying to SEND")
console.warn(processedHtml)
},
}
}
This outputs the raw html string without the styles, so I figured I really need to pass the compiled css for the inliner to do its job
<div><h1 class="text-xl font-bold border-b">You have a new Test Email on Productlane</h1><p class="border-b">Something something</p>Open</div>
You can see from the snippet I'm trying to use Juice to inline the styles, however, I can seem to get the classes to be rendered in the html, any idea how to achieve this?
Right I’ve been doing some digging and this is my plan for handling emails.
Transpile down styles sheets to style attribute using Juice, abstract HTML4 tables as react components to allow full email client support.
Support {{ parameters }} leave them in your outputted HTML and pass it through Handlebars to replace them just before sending the email.
Option 1:
Use NextJS static html export to generate HTML files from said React components.
Configure build command to run custom Juice script on outputted files.
Reference the exported files using handlebars to apply the per user context e.g. { name: “David” }. I’m doing this in my sendEmail() function.
Option 2
Use NextJS server endpoint to compile the handlebars template with the per user context. See this article for reference.
You could also replace custom Juice script with this CLI tool or this npm package. Optionally you can even use Inky to abstract away HTML4 tables.
Alternatively if you only want partial email client support NextJS can inline the CSS into the head with this experimental flag discusses here. For full support you will need CSS in style attribute.
I have a lambda function sendEmail(email: string, templateName: string, context: Record<unknown, any>) which has the hubs template files bundle inside it. When the email is sent it then process the context using handlebars compile().
I have a legacy PHP app which I would like to slowly migrate to Vue. The PHP app renders a bunch of HTML and javascript files in quite a tangled fashion, i.e.
foo.js.php
...
<script src="mysite.com/some_js_file.js" />
...
const a = '<?=$variable_from_php?>';
so in the end, the browser obviously doesn't know how the js files are constructed, but can run them. What I'd like to do is from the outer layer Vue app, request the index page for a certain sub-section of the legacy app, and render that to a Vue node, as a micro-frontend of sorts. When I request each index, it will of course, contain a header with numerous other imports (scripts/styles) that that micro-frontend needs to function. So, two parts to this question: 1) what would be the best (or maybe least terrible) way to do this in Vue. Using v-html? iframe? (please say no iframes) And 2) will there be any showstopper security problems with this approach (since I'm basically saying fetch all the JS in the header and run it). Let me know if this question makes sense. Thanks!
Maybe you need like to : a module php or component as template.php(php server)
export const templateOfAdvanceTemplatePage = `
<div class="content edit-page management">
<md-card class="page-card">
<?php echo "My Component" ?>
</md-card>
</div>
And from node server
import * as url from "url";
var templateOfAdvanceTemplatePage = url.parse("http://www.website.com/template.php");
export default {
template: templateOfAdvanceTemplatePage,
...
}
for more information import vue here, and php as javascript file here
Vue.js can be used in two separate ways: For more complex applications you would use a build process and pre-compile the templates from the source, which are usually Single File Components SFC; *.vue files. The templates would then become render functions and no HTML is ending up in the output assets. There is, however, another way of defining Vue components. You can define them inline with the runtime-only bundle of Vue. For migrations and smaller applications this approach would be advised. You would need to include the compiler. See also the Vue documentation about that topic Vue v2 and Vue v3). If you are importing Vue as a module and are missing the compiler, see here.
If you want to render dynamically generated HTML from PHP as a Vue template, you would need the second approach. Keep in mind that, with this approach, you would always need to have the generated PHP output to be in sync with the Vue components. And you would need to fully trust the HTML, you are generating with PHP, otherwise you will risk injections.
There is, however, still another problem: You need the generated PHP output HTML as a string within JavaScript and it should not be interpreted by the browser (ideally) or removed again from the DOM. So, you need to decide (based on your project) how you want to generate the HTML so that it can be read in as a JavaScript string. Here are some approaches:
Generate the HTML directly into the page. Then, define which element you want to target, get the HTML with .innerHTML and delete the node from HTML (drawback: you will render the HTML twice, might produce short visual glitches).
Fetch the HTML via XHR from a separate page. You will directly have the HTML as a string in the response (see e.g. fetch).
Render <script type="text/x-template" id="static-html-content"></script> around the generated HTML content. Then, you do not need the HTML as string and you can directly use the id as reference (use template: '#static-html-content'). See the documentation of X-Templates in Vue.
Then, you can use the runtime-only version of Vue and define your components. Here is a live example:
const Counter = {
// retrieve and add your template string here
template: `
<div class="counter">
This is a counter: {{ counter }}
<button #click="counter++">Increase Counter</button>
</div>
`,
data: function() {
return {
counter: 0
}
}
};
const App = {
components: { Counter },
template: `
<div class="app">
This is the app component.
<hr />
<counter />
</div>
`
};
new Vue({
el: '#element',
template: '<App />',
components: { App }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="element"></div>
Another approach would be to just render the HTML string within a component with the v-html attribute. The main drawback of this solution is, however, that the content is then not reactive. You cannot change your internal component data and expect the template to react to the changes. Therefore, you are missing out on the main benefits of Vue, but you are not restricted to a template which matches your components internal structure.
A similar question was also posed in the Vue forum: link
Im not really sure if Im understanding correctly the way observables work and how to get references from mounted tags. I have a component. Within this component we have a component and a component. The purpose is to avoid coupling between components. Because of that, I would like that my search component triggers an event when a search is done(a button is clicked). This event should be caught by the component which will filter the collection data based on the search.
The index.html file load the tag by using:
index.html
riot.mount(".content", "page", null);
The page is defined as follow:
page.js
<page>
<!-- Search tag controls -->
<search id="searchTag"></search>
<!-- Collection data to display -->
<collection id="collectionTag"></collection>
</page>
The component script is briefly defined like:
search.js
var self = this;
riot.observable(self);
<!-- This function is called when the user click on the button. -->
self.filtering = function()
{
<!-- We get data from inputs -->
var info = Getting data from inputs;
<!-- Trigger the event hoping that someone will observe it -->
self.trigger("filterEvent", info);
}
How can I make the component observe for that event?
To me it seems that I should be able to get references from search tag and collection tag in the page.js. By doing so I could connect the events like follow:
searchComponent = riot.mount('search');
collectionComponent = riot.mount('collection');
searchComponent.on('filterEvent', function()
{
<!-- Trigger function to filter collection data -->
collectionComponent.trigger('filterData');
});
Right now I cannot make it work like that.
At the point of execution, searchComponent and collectionComponent are not defined.
I tried also getting references of these component by using this.searchTag and this.collectionTag instead of mounting them but at the time the code is executed, the components have not been mounted and so I dont get a reference to them.
Any ideas to make it work?
Inspired by the answer given by #gius, this is now my preferred method for sending events in RiotJS from one tag to another.. and it is great to work with!
The difference from #gius approach being that, if you use a lot of nested tags, passing a shared Observable to each tag falls short, because you would need to pass it again and again to each child tag (or call up from the child tags with messy this.parent calls).
Defining a simple Mixin, like this (below), that simply defines an Observable, means that you can now share that in any tag you want.
var SharedMixin = {
observable: riot.observable()
};
Add this line to your tags..
this.mixin(SharedMixin);
And now, any tag that contains the above line can fire events like..
this.observable.trigger('event_of_mine');
..or receive events like this..
this.observable.on('event_of_mine',doSomeStuff());
See my working jsfiddle here http://jsfiddle.net/3b32yqb1/5/ .
Try to pass a shared observable to both tags.
var sharedObservable = riot.observable();
riot.mount('search', {observable: sharedObservable}); // the second argument will be used as opts
riot.mount('collection', {observable: sharedObservable});
And then in the tags, just use it:
this.opts.observable.trigger('myEvent');
this.opts.observable.on('myEvent', function() { ... });
EDIT:
Or even better, since your search and collection tags are child tags of another riot tag (page) (and thus you also don't need to mount them manually), you can use the parent as the shared observable. So just trigger or handle events in your child tags like this:
this.parent.trigger('myEvent');
this.parent.on('myEvent', function() { ... });
Firstly I do not understand your file structure !
In your place I would change filenames :
page.js --> page.tag
search.js --> search.tag
And i dont see your search tag in search.js code.
So I dont see your Collection tag file ...
Are you sure that this one use this code ?
riot.observable({self|this});
Because it's him who will receive an Event.
For me when I use Riot.js(2.2.2) in my browser, if I use
searchComponent = riot.mount('search');
searchComponent will be undefined
But with this code you can save your monted tag reference :
var searchComponent ={};
riot.compile(function() {
searchComponent = riot.mount('search')[0];
});
Another option is to use global observables, which is probably not always best practice. We use Riot's built in conditionals to mount tags when certain conditions are met rather than directly mounting them via JS. This means tags are independent of each other.
For example, a single observable could be used to manage all communication. This isn't a useful example on its own, it's just to demonstrate a technique.
For example, in a plain JS file such as main.js:
var myApp = riot.observable();
One tag file may trigger an update.
var self = this;
message = self.message;
myApp.trigger('NewMessage', message);
Any number of other tag files can listen for an update:
myApp.on('NewMessage', function(message) {
// Do something with the new message "message"
console.log('Message received: ' + message);
});
Maybe overkill but simple. let riot self observable
riot.observable(riot);
So you can use
riot.on('someEvent', () => {
// doing something
});
in a tag, and
riot.trigger('someEvent');
in another.
It's not good to use global variable, but use an already exists one maybe acceptable.
I've been going through this tutorial on ReactJS.NET, and hit a snag. It mentions that:
We will use simple polling here but you could easily use SignalR or other technologies.
While this works when I do client-side rendering, it throws the following error when rendering server-side. Currently, I don't actually need jQuery or SignalR to render the initial state as I'm only using them to subscribe to updates once the app is running. I guess my question is, what is the correct way to structure my React application so that I can render it server-side or client-side at will.
Error while loading "~/Scripts/jquery-1.10.2.js": ReferenceError: window is not defined
Got it working (live demo), I just needed to move the call to React.render outside of the jsx file and pass in what I needed (see snippet below). Another option would be to try and mock the expected objects with jsdom.
<!-- Render the React Component Server-Side -->
#Html.React("CommentBox", new
{
data = Model,
conn = false
})
<!-- Optionally Render the React Component Client-Side -->
#section scripts {
<script src="~/Scripts/react/react-0.12.2.js"></script>
#Scripts.Render("~/bundles/comments")
<script>
React.render(React.createElement(CommentBox, {
data: #Html.Raw(Json.Encode(Model)),
conn: $.hubConnection()
}), document.getElementById("react1"));
</script>
}
Using jQuery while rendering server side using reactjs.net:
The answer is a partial Yes if you put the jQuery in the ComponentDidMount function of React with your setup above.
Like this:
componentDidMount: function(){
if (this.props.userID == 0){
$("#postButton").hide();
}
}
It also worked in some other places but not everywhere. Other places in the React script, I got "ReferenceError: $ is not defined".
Here's some additional comments by the reactjs.net author himself. Basically, jQuery is not designed to work server side so prob best not to rely on it.
https://groups.google.com/forum/#!topic/reactjs/3y8gfgqJNq4
As an alternative, for instance, if you want to control the visibility of an element without using jQuery, you can create a style and then assign the style based on If logic. Then add the style as an inline attribute to the element as shown below. This should work fine in React.
var styleComment = {display: 'block'};
if (this.props.commentCount == 0){
styleComment = {display: 'none'}
}
<div style={styleComment}>Count is greater than 0</div>
Once I register a helper function for Handlebars using Handlebars.registerHelper(), is it possible for me to change and/or remove the helper? Can I just use registerHelper() again to overwrite the current helper, or is there such a thing as Handlebars.unregisterHelper()? Or should I use a different approach if I need a helper to change during an application?
The use case for me is with the Iron Router plugin for Meteor. I am using a layoutTemplate as the general structure of my page. I wanted to use a helper in the layout template right before I yield the main content of the page body (via a <template>, per se) so that each individual template can define its own page title but not have to specify the location in the page every time. For example, my layout template could look like this:
{{pageTitle}}
{{yield}}
And then in the .js file for the rendered template, I would use the following to fill in the {{pageTitle}} placeholder:
Handlebars.registerHelper("pageTitle", function() {
return "My Page Title";
};
Perhaps there is an alternative way to solve this problem.
What you can do is something like this
Handlebars.registerHelper("pageTitle", function() {
return Session.get('pt');
};
function changePageTitle(str){
Session.set('pt', str);
}
Meteor, being reactive, should update the page when a session variable changes. When you switch to another page, simply run changePageTitle.