Basically i would like to render the title element into the head element like this
<head>
<HeadFragment />
</head>
this is only possible at the moment by actually rendering as the component, but this will cause all kinds of issues if you use external scripts that inject into the head.
i basically want
var HeadFragment = React.createClass({
render: function () {
return (<fragment>
<title>{this.props.title}</title>
... meta ...
... styles ...
... scripts ...
</fragment>)
}
});
React.render(<HeadFragment />, document.querySelector('head'));
but the fragment node should not be an actual DOM node, instead it would be a document fragment.
without this support it makes full page rendering pretty much impossible, and forces us to do a bunch of other stuff in order to modify things like this without breaking the HTML spec.
Modifying document fragments like the whole html container, head, and such are just completely undependable. There's been a lot of chatter about this in the past like here: https://groups.google.com/forum/#!topic/reactjs/4jI5xe7TXzQ and on GH and such.
There are projects like https://github.com/matthewwithanm/react-frozenhead which try to do the correct updates based on what you provide, like changing the title using document.setTitle and such, but yeah.
Related
I have a chat widget that I want to embed it other people's websites. It looks just like Intercom and all the other chat popups. I want to make the chat popup stick to the bottom-right hand corner of the screen regardless of where you scroll. However, when I import the chat app as an iframe and give it position: fixed; bottom: 0px; right: 15px;, the iframe does not go to where I expect it to go.
I realize that iframes are suboptimal for embedded JS widgets, and all the best embedded apps are importing .js files from file storage. After searching online for hours I have yet to find an explanation/tutorial on how to make those JS files that hook onto a and render the widget. How do you even make one of those pure javascript apps, and what are they called? (Not web components I assume, because there have been widgets for a long time).
Sorry if this question is kinda noob. I never knew this was a thing until I tried implementing it myself. Can anyone point me in the right direction on how to get started making JS web widgets? Thank you! (Maybe a ReactJS to VanillaJS converter would be super cool)
A pure Javascript App is called a SPA - Single Page Application - and they have full control over the document (page). But since you ask about embeding a widget, I don't think that is what this question is about (there are tons of info. on the web on SPAs).
I was going to suggest that going forward you do this using Web Components - there are polyfills available today that make this work on nearly all browsers - but since your question mentioned that you wanted to know how it is done without it, I detail below one of my approaches.
When creating a pure JS widget you need to ensure that you are aware that a) you do NOT have control over the global space and b) that it needs to play nice with the the rest of the page. Also, since you are not using Web Components (and are looking for a pure javascript (no libs)), then you also have to initialize the widget "manually" and then insert it to the page at the desired location - as oposed to a declaritive approach where you have an assigned HTML tag name for your widget that you just add to the document and magic happens :)
Let me break it down this way:
Widget Factory
Here is a simple Javascript Widget factory - the create() returns an HTML element with your widget:
const Widget = Object.create({
create(chatId) {
const wdg = document.createElement("div")
wdg.classList.add("chat-box");
wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
// Load your chat data into UI
return wdg;
}
});
To create a new widget (HTML Element) using the above you would:
const myWidgetInstance = Widget.create("chat-12345");
and to insert this widget into the page at a given location (ex. inside of a DIV element with id "chat_box", you would:
document.getElementById("chat_box").appendChild(myWidgetInstance);
So this is the basics of creating a Widget using the native (web) platform :)
Creating a reusable/embeddable Component
One of the key goals when you deliver a reusable and embeddable component is to ensure you don't rely on the global space. So your delivery approach (more like your build process) would package everything together in a JavaScript IIFD which would also create a private scope for all your code.
The other important aspect of these type of singleton reusable/embeddable components is that your styles for the Element needs to ensure they don't "leak" out and impact the remainder of the page (needs to play nice with others). I am not going into detail on this area here. (FYI: this also the area where Web Component really come in handy)
Here is an example of a Chat component that you could add to a page anywhere you would like it to appear. The component is delivered as a <script> tag with all code inside:
<script>(function() {
const Widget = Object.create({
create(chatId) {
const wdg = document.createElement("div");
wdg.classList.add("chat-box");
wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
// Load your chat data into UI
return wdg;
}
});
const myWidgetInstance = Widget.create("chat-12345");
const id = `chat-${ Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) }`;
document.write(`<div id="${ id }"></div>`);
document.getElementById(id).appendChild(myWidgetInstance);
})();</script>
So you could use this in multiple places just by droping in this script tag in the desired locations:
<body>
<div>
<h1>Chat 1</h1>
<script>/* script tag show above */</script>
</div>
...
<div>
<h1>Chat 2</h1>
<script>/* script tag show above */</script>
</div>
</body>
This is just a sample approach of how it could be done. You would have to add more in order to support passing options to each widget (ex. the chat id), defining styles as well other possible improvements that would make the runtime more efficient.
Another approach
You could add your "script" once and wait for the rest of the page to load, then search the document for a "known" set of elements (ex. any element having a CSS Class of chat-box) and then initialize a widget inside of them (jQuery made this approach popular).
Example:
Note how data attributes can be used in DOM elements to store more data specific to your widget.
<div class="chat-box" data-chatid="123"></div>
<script>(function() {
const Widget = Object.create({
create(chatId) {
const wdg = document.createElement("div");
wdg.classList.add("chat-box");
wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
// Load your chat data into UI
return wdg;
}
});
const initWhenReady = () => {
removeEventListener("DOMContentLoaded", initWhenReady);
Array.prototype.forEach.call(document.querySelectorAll(".chat-box"), ele => {
const myWidgetInstance = Widget.create(ele.dataset.chatid);
ele.appendChild(myWidgetInstance);
});
};
addEventListener('DOMContentLoaded', initWhenReady);
})();</script>
Hope this helps.
The best way to create Javascript widget without third-party library is to create Custom Elements.
The following link : Custom Elements v1 is a good introduction to this technology.
See a minimal example below:
class Chat extends HTMLElement {
connectedCallback () {
this.innerHTML = "<textarea>Hello</textarea>"
}
}
customElements.define( "chat-widget", Chat )
<chat-widget>
</chat-widget>
I have a Meteor app, all UI components are built with React. I use FlowRouter for routing.
Now I want to add analytics with Yandex.Metrika service.
I found this package for React: https://github.com/narkq/react-yandex-metrika
But I how I have to use it? For what reason do I need <YM /> component from this example?
import {Initializer as YM} from 'react-yandex-metrika';
// Use the initializer to add the script to your page somewhere.
var MyComponent = React.createClass({
render: function() {
return (
<div>
// SNIP
<YM />
// SNIP
</div>
);
}
});
And where should I initialize the tracker object? I read this:
// This is supposed to be executed only in browser and only once.
// Because of that, the most sensible place for this code is right after you javascript bundle.
ym.init([987654321]);
But what is javascript bundle and where should I place my im.init(id) code?
Actually all what I need is to have funsctions to send data to Metrika, such as hit (pageview analog rom ga), reachGoal and so on.
thank you for your answers!
For what reason do I need component from this example?
You need it to load metrika's main code
How metrika works:
webmaster (you) inserts small piece of js code (loader) to all pages. this code append async script with main code (it's a bit bigger than loader) and create instance of metrika object ('counter') - new Ya.Metrika(...params). Instance will be available in global variable named yaCounterXXXXX, where XXXXX is your counter's id.
So, <YM /> component is loader from previous paragraph.
Actually all what I need is to have funsctions to send data to
Metrika, such as hit (pageview analog rom ga), reachGoal and so on.
There is doc about that at the bottom of readme. But I don't see filter by counter id for that methods. Maybe I make a pr to add this functionality. In any case you can use global variable yaCounterXXXXX like this yaCounterXXXXX.hit(url, params) or yaCounterXXXXX.reachGoal(goalId, params)
I hope I helped you.
I am working on a ReactJS app for a client. I want the client to be able to customize some of the configurations and templates in the app. So I have created a config.js file
window.APP_CONFIG = {
url: 'example.com',
template: {
item: '
<div>
<h3>A new item title</h3>
{ item.description }
</div>
'
}
};
How can I use this variable in my react app? I have tried the following but it gives me errors.
var app = React.createClass({
render(){
return (
<div>
{APP_CONFIG.template.item}
</div>
)
}
})
Anyone has done something similar?
You are not alone. React Templates allows you to use react without having to put up with inline jsx.
The react-templates syntax is very very similar to jsx but allows you to have your component html code in a separate .rt file - much cleaner. RT files have better support for looping and branching. Added bonus, you do not have to dance around reserved attribute names like "class" and "for" and there are other advantages.
From the docs:
Why not use JSX?
Some love JSX, some don't. We don't. More specifically, it seems to us
that JSX is only a good fit for components with very little HTML
inside. And this can be accomplished by creating DOM elements in code.
Also, we like to separate code and HTML because it just feels right.
The main gotcha I've seen so far is that attribute values like onMouseDown={someJsExpr} are magically quoted in jsx but in react-templates you have to explicitly quote them a la onMouseDown="{someJsExpr}", no magic. I found that it was not hard to find cases of this and convert them over.
I need to implement a browser-like banner warning system so that the user can only access certain features after they've acknowledged certain warnings. The warning messages will depend on data sent from the server and I have to implement different styles and links.
Edit: There could be any number of warnings displayed at the same time. One for each feature. The user must individually acknowledge each warning before the corresponding feature is enabled. Some of the warning texts are static, some are dynamic. Some can have different acknowledge links instead of the standard "okay".
Traditionally, I would package each kind of warning into a class (in the OO sense) and push them to screen through a centralized method, e.g.
displayWarning(new InfoBanner("You must ... before you can modify " + data.name, function onclick() { ... }));
Here the InfoBanner class will have a method that creates the banner elements and attach the event handler.
The Angular way of doing this, on the other hand, seems to be you write the banner entirely in HTML with ng-if and ng-click, etc. E.g.:
<p style="info banner" ng-if="...">You must ... before you can modify {{...}}. <a href ng-click="...">Okay</a></p>
However, this seems quite unfocused and messy because there will now be a large blob of banner code dwarfing the functional part of the page. (There are hundreds of error types defined!)
Is there any way to resolve this without reverting to the fully imperative code?
(Note: a custom directive is probably not the answer as <p style="info banner" is almost like a directive and there's little sharable code among these warnings beyond this.)
(Edit: One can see this question in another way: in the imperative world, the warning-adding logic are scattered in the code but close to the feature they're protecting, so they're easy to understand and maintain. In the declarative world, they must be centralized to the place where they're displayed. I would like a solution where they're declared close to the component they're protecting but displayed centrally.)
What I understand from your question your problem is that since you are in an Angular application you need / should include your banners as markup in your HTML view, since however whether each banner is displayed or not depends on the data you are getting from the server you only know if a specific banner should be displayed in your controller (hence the ng-if you have included in your banner HTML example).
What I would propose in this case would be to create a BannerService which would hold a list of all the banners that should be displayed at any given time. In your controller you can use the functions exposed by the service to add banners to the list when the data you got from the server indicates that you should do so. Each banner in the list would be an object containing all the information that might be different between different banners (ex. banner text, type, etc.) meaning that your HTML view doesn't really need to "know" anything about specific banner details and can just display all the banners available in the BannerService using an ng-repeat.
You can see below a quick-and-dirty example to better understand how this would work.
var app = angular.module('TestApp', [])
app.service('BannerService', [function(){
var banners = [];
this.getBanners = function() {
return banners;
}
this.addBanner = function(banner) {
banners.push(banner);
}
}])
app.controller('TestCtrl', ['BannerService', function(BannerService) {
// Add banners to the banner service depending on your data etc.
BannerService.addBanner({text: "This is a banner", type: "info"});
}])
app.controller('BannerCtrl', ['$scope', 'BannerService', function($scope, BannerService) {
$scope.banners = []
$scope.$watch('BannerService.getBanners', function (newVal, oldVal, scope) {
$scope.banners = BannerService.getBanners();
});
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="TestApp">
<div ng-controller="BannerCtrl">
<div ng-repeat="banner in banners"><p class="banner {{banner.type}}">{{banner.text}}</p></div>
</div>
<div ng-controller="TestCtrl">Page content</div>
</div>
In my opinion what you are looking to implement fits perfectly with Aspect Oriented Programming. If you've never used AOP before be prepared for some light reading. The concept is simple and works very well with Angular's patterns. There is an AngularAOP project, but before you dive into it I suggest running through this article first:
http://www.bennadel.com/blog/2425-decorating-scope-methods-in-the-angularjs-prototype-chain.htm
I have created a Backbone.js/Require.js application that dynamically loads HTML templates to use as "pages" in the application. This means my main HTML page looks like so.
<head>
// Necessary CSS and Javascripts here
</head>
<body>
<div id="container"></div>
</body>
And then I used underscore templates to render new elements dynamically to the DOM. However, a new feature requires the use of a Raphael.js chart. I created a new element <div id='canvas'></div> and call Raphael('canvas') but since the canvas element wasn't there on DOM ready, Raphael can't see the newly created element.
I have attempted to use a jQuery selector in place of the id reference like so Raphael($('#canvas')) but this attaches the canvas to the body element and not my container element.
Any suggestions on how to bind a Raphael canvas to a dynamically created element?
Raphael($('#canvas').first(), '100%', '100%')
Though I had errors else where, the main issue that caused Raphael not to fire was forgetting that a jQuery selector passes an array of Elements and Raphael's constructor want's a single element. Raphael was attaching itself to the body because it was the top level parent of the selector's result.
Mosselman was also correct in pointing out that you can build a view in Backbone entirely in memory and then append it to the DOM.
A way to overcome this issue is by creating an empty element in the view and binding everything onto that. I have never worked with Raphael, but I think this could work:
var someView = Backbone.View.extend({
el: document.createElement('div'), // This creates a DOM element '<div></div>'
initialize: function(){
Raphael(this.el); // Attach Raphael, you could also go with jQuery
},
render: function(){
jQuery('#container').append(this.el); // Add to DOM somehow
}
})
seems like a good approach is to either throw an event after the template has been added to the DOM and have your call to Raphael('canvas') listen for that event or use a callback to trigger Raphael('canvas'). in both cases you are ensuring that you don't call Raphael('canvas') before the target element is in place.
very roughly, something like this:
//from your raphael module / code
$(document).on('canvasAdded', function(){
var paper = Raphael('canvas');
//stuff!
});
//after you are sure your template has rendered
$(document).trigger('canvasAdded');
you probably want to make some kind of .init() method and call that from the event handler (vs. what I show above) but hopefully this points you in the right direction.
I know that this is too old question, but anyway it can help to someone. Its important to be sure that your view is placed on page, so use something like onShow functionality, or render. But anyway Raphael will not show right because if your send to Raphael this.$el or anything similar it will not accept it like you expect. What You need to do is something like this.$el.first() or this.$el[0].