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
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().
TL;DR
I need to make Angular evaluate/parse HTML elements with Angular markup that are dynamically added to the DOM by treant-js.
[UPDATE 30/09/17]
I've followed the solution proposed there (which involves dynamic module/component creation). However, there are more and more issues with this implementation.
First, I'm injecting to the sub-component the needed dependances declared for the parent module (Router, ActivatedRoute, etc.):
this.addComponent(
this.treeContainer.nativeElement.outerHTML,
{},
this.conversation.threads,
this.route,
this.router,
this.ws
);
This seems wrong to me, because the most logical way would be to inject these dependancies directly into the dynamic component (with imports in the dynamic module), but I couldn't manage to do this.
Also, because treant-js needs to directly attach to the DOM the new HTML components it creates (I guess in order to bind the events properly), I'm unable to retrieve the HTML string before it's in the DOM. So, the ugly fix I found was:
to extract the HTML string with this.treeContainer.nativeElement.outerHTML(see the code snippet above) then send it as the template parameter for the dynamic component ;
since there would be 2 trees (because of 1.), to remove the initial treeContainer handled by treant-js: $(this.treeContainer.nativeElement).remove();.
Where then are we?
Besides the issues mentionned earlier, the solution I've tried is not at all acceptable. It seems that treant-js needs to attach its HTML elements to the DOM for event bindings and tree storage/management, so when I duplicate the HTML string of the tree to form the template of a dynamic component, this 2nd tree is well evaluated by Angular, but not under treant-js framework anymore (e.g.: some functionalities like collapsable handles would no longer work).
To sum up: I'm unable to have both Angular and treant-js working at the same time to handle my tree markup.
Moreover, the ugly trick I use to avoid having duplicate trees causes other issues, when it comes to re-generate the tree, whether it be from a dynamic update (new node created by user interaction), or from navigating to the same page multiple times (routing).
Last but not least, I can't even use some Angular markup like ngModel which come from imported modules (e.g. FormsModule): I get an error saying ngModelisn't bound to input element. The module is of course imported in my parent component, and I tried to import it in the dynamic module as well:
private addComponent(template: string, properties: any = {}, threads: Thread[],
route: ActivatedRoute, router: Router, ws: WebSocketService) {
#Component({template})
class TemplateComponent implements OnInit {
//...
#NgModule({
declarations: [TemplateComponent],
imports: [FormsModule]
})
class TemplateModule {}
const module = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = module.componentFactories.find(componentFactory =>
componentFactory.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
Unless I find soon a way to make both Angular and treant-js work together smoothly, I may have to use another library such as angular-tree-component or ng2-tree. The big downside then is that I may miss some visual functionalities (like tree orientation) and interactivity I found easy to implement with treant-js.
[First post]
I'm new in Angular2, so there are certainly concepts I still don't understand correctly. I'm using treant-js in my own Angular2 project, and I'm stuck at making Angular2 evaluate/compile the directives in the generated HTML by treant-js.
I've seen this : Integrating Treant-js in Angular2 Project to integrate treant-js into the project, but I can only use some static HTML instead of routerLink, ngIf or ngFor for instance.
I call buildConversationTree in the ngInit of my component :
buildConversationTree() {
const config = {
container: '#tree',
connectors: {
type: 'step'
},
node: {
HTMLclass: 'thread-node',
collapsable: true
},
levelSeparation: 45
};
let treeConfig = this.buildNodes(this.conversation.threads);
treeConfig.unshift(config);
const tree = new Treant(treeConfig, this.onTreeLoaded, $);
}
Basically, the buildNodesmethod generate the HTML for each node of the tree, by calling the nodeHTMLmethod :
nodeHTML(data) {
return `
<div id="thread${data.id}" class="thread-node-wrapper">
<a class="main-link" routerLink="../thread/${data.id}">
Lorem ipsum
</a>
</div>
`;
}
I've got a simple template for my component:
<div class="tree-container">
<div id="tree"></div>
</div>
which becomes after the tree generation:
<div class="tree-container">
<div id="tree" class=" Treant Treant-loaded"><svg height="598" version="1.1" width="633" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="overflow: hidden; position: relative;"><desc style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">Created with Raphaël 2.1.4</desc><defs style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></defs></svg><div class="node thread-node" style="left: 191.5px; top: 224px;">
<div id="thread60" class="thread-node-wrapper">
<a class="main-link" routerlink="../thread/60">
Lorem ipsum
</a>
</div>
</div></div>
</div>
And the routerlink="../thread/60"remains uncompiled/recognized by Angular.
In Angular.js:
Back in Angular.js (which was used for the project before), we would solve the problem using $compile :
var treeIsLoaded = function () {
// ...
var e = $('#tree')[0];
$compile(e)($scope);
if (!$scope.$$phase) {
$rootScope.safeApply();
}
// ...
};
and all the directives like ng-click, ng-show, etc., worked out, so I'm a bit puzzled about how hard it is to make it work in Angular2.
What I tried:
https://angular.io/guide/dynamic-component-loader, but unsuccessfully, even by importing the tree from another component didn't solve the issue. My guess is that the directives are not directly coded in the template itself but generated by treant-js, which is a 3rd library manipulating the DOM outside of the Angular framework ;
How can I use/create dynamic template to compile dynamic Component with Angular 2.0?, but I'm not sure this solution shall solve my issue, I'm a bit afraid of its complexity to be honest ;
angular 2 html binding, I tried to manually copy/paste the view element into a named template with ViewContainerRef members in my component, but it would work.
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.
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>
I am new to Javascript and just started fiddling around with Meteor out of curiosity. What really surprises me, is that it seems that all HTML content gets combined into a single page.
I suspect there is a way to introduce some handling of URLs directing to special pages. It seems that the "todo" example is capable of doing this via some kind of Router class. Is that the "canonical" way of URL handling?
Assuming I can handle URLs, how would I structure my HTML code to display separate pages? In my case they could each have completely separate sets of data, so no HTML code needs to be shared at all.
Jon Gold's answer used to be correct, but as of Meteor 0.5.4:
Work has now shifted to Iron Router. Please consider using IR instead of Router on new projects!
Thus, the current "canonical" way to do this is probably to use IronRouter.
As far as I am aware, there is currently no out of the box way to do this.
What I suggest to do, is to use Backbone.js smart package.
Backbone.js comes with the push-state Router, and if the user's browser doesn't support that it will fallback to hash urls.
In your meteor app directory type this meteor add backbone.
Then somewhere in your client-side code create a Backbone.js Router like so:
var Router = Backbone.Router.extend({
routes: {
"": "main", //this will be http://your_domain/
"help": "help" // http://your_domain/help
},
main: function() {
// Your homepage code
// for example: Session.set('currentPage', 'homePage');
},
help: function() {
// Help page
}
});
var app = new Router;
Meteor.startup(function () {
Backbone.history.start({pushState: true});
});
Then somewhere in your Handlebars template, you can create a helper that will render a page based on the value set in Session's "currentPage".
You can find more information about backbone.js router here: http://backbonejs.org/#Router
Also relevant information on how to create a Handlebars helper method in Metoer here: http://docs.meteor.com/#templates
Hope this helps.
Meteor-Router makes this really easy. I've been using it in some apps I've been building with Telescope as a good reference. Have a look at Telescope's router.js
To use it…
mrt add router
In client/router.js:
Meteor.Router.add({
'/news': 'news', // renders template 'news'
'/about': function() {
if (Session.get('aboutUs')) {
return 'aboutUs'; //renders template 'aboutUs'
} else {
return 'aboutThem'; //renders template 'aboutThem'
}
},
'*': 'not_found'
});
In your template…
<body>{{renderPage}}</body>
I found the same problem. When the code gets bigger it is difficult to keep the code clean.
Here goes my approach to this problem:
I separate the different html pages as I would do with another web framework. There is an index.html where I store the root html page. And then for each big functional part I create a different template and place it in one different html. Meteor then merges them all. Finally I create a session variable called operation where I define what to show at each time.
Here goes a simple example
index.html
<head>
<title>My app name</title>
</head>
<body>
{{> splash}}
{{> user}}
{{> debates}}
</body>
then in splash.html
<template name="splash">
{{#if showSplash}}
... your splash html code goes here...
{{/if}}
</template>
then in user.html
<template name="user">
{{#if showUser}}
... your user html code goes here...
{{/if}}
</template>
and so on ...
In the javascript code then I check when to print each template using the Session variable, like this:
Template.splash.showSplash = function(){
return Session.get("operation") == 'showSplash';
}
Finally the Backbone Router manages this Session variable
var DebateRouter = Backbone.Router.extend({
routes: {
"": "showSplash",
"user/:userId": "showUser",
"showDebates": "showDebates",
// ...
},
splash: function () {
Session.set('operation', 'showSplash');
this.navigate('/');
},
user: function (userId) {
Session.set('operation', 'showUser');
this.navigate('user/'+userId);
},
// etc...
});
I hope this pattern is helpful for other Meteor developers.
This is my hacky solution to routing :
https://gist.github.com/3221138
Just put the page name as the template name en navigate to /{name}