How to migrate jQuery functions and events in gatsby? - html

I am currently migrating an old but still beautiful website to Gatsby PWA.
The old website was built purely in HTML and jQuery, and it has many beautiful animations with jQuery functions.
Technically, I succeeded in importing some external scripts including jQuery(v3.3.7) and Bootstrap(3.3.1) by customizing html.js.
And also I updated some attribute names like class to className, style - string to object, etc.
What I think a bit difficult is to use jQuery functions with events like onmouseover in the old HTML files.
For example,
...
<div className="section">
<div className="block_information" id="block_information">
<div className="row first_row">
<div className="col-md-2 col-xs-12 icon_block_information top">
<div className="svg_text" onmouseover="runSmile()">
~~~~~~~~~~~~~~~~~~~~~~~~
<svg viewBox="0 0 80.25 80.25">
...
<g id="icon_smile_1">
<path
id="icon_smile"
className="cls-2"
...
And runSmile function looks like,
function runSmile() {
window.ks = (function () {
function z(a) {
return "undefined" !== typeof a;
}
function v(a, b) {
return a && 0 == a.indexOf(b);
}
function N(a) {
if (!isFinite(a)) throw "non-finite value";
}
function O(a) {
if (14 >= a) return 16;
(a = X[a]) || (a = 0);
return a;
}
function D(a) {
return 0 <= a ? Math.pow(a, 1 / 3) : -Math.pow(-a, 1 / 3);
}
...
document.ks = ks;
(function (ks) {
ks.animate(
"#icon_smile",
[{ p: 6, t: [0, 1000], v: [0, 360], e: [[0], [0]] }],
{
autoplay:
document.location.search
.substr(1)
.split("&")
.indexOf("autoplay=true") < 0,
},
);
})(ks);
}
In this case, can I use the jQuery functions with only small updates and also without updating event attributes like onmouseover to onFocus?
If I have to update the jQuery functions and event attributes, what should I do?
Thanks in advance.

onmouseover should be replaced by onMouseOver or onMouseEnter/onMouseLeave.
However, I would discourage you from importing jQuery and Bootstrap into a React project (really, don't do it).
jQuery and Bootstrap point and manipulates directly the real DOM, while React creates and manipulates a virtual DOM (vDOM). Because of that, React will never be aware of changes that jQuery does to the DOM and vice-versa, which translates into hydration issues. Practically, this means that your components (or part of them) may not be rendered when you want or in your different use-cases, especially when you perform some kind of navigation through pages, losing control of your code-flow.
In addition, because of that different way of working (DOM vs vDOM), jQuery functions applied into React environment will be never unmounted. This may sound meaningless but your resources will keep getting accumulated and eventually your site will become extremely slow, especially for users that remain more than X seconds.
Both (jQuery and React) have different purposes and you shouldn't mix them, it will lead you to huge caveats and headaches.
Moreover, window in Gatsby needs to be treated specially (like any other global objects, like document) because of the SSR (Server-Side-Rendering), where there's no window because it's not even defined yet. Depending on your triggers and use-cases, you may need to wrap it inside:
if(typeof window !== 'undefined'){
// your window stuff
}
If you still keep working with jQuery in a React environment you can follow: https://reactjs.org/docs/integrating-with-other-libraries.html
Where it provides some useful hints to unmount all the jQuery functions.
Regarding Bootstrap, I would recommend using the React-based version to avoid the same hydration issues that I explained before.

Related

Using methods in html template angular 2+

What is the difference between using methods in html template that return true or false, and writing directly these conditions in html template angular 2+?
It is particularly interesting how often the first and the second method will be called?
example:
<div *ngIf="array && array.length && (array.property === true)">test</div>
or
<div *ngIf="isArrayProperty()">test</div>
public isArrayProperty() {
return array && array.length && (array.property === true);
}
The primary difference as Alexander Staroselsky has pointed out is maintainability and readability. In general logic should reside within your type script files, and not within your view. The more you can break out logic and view the easier your site will be to maintain.

React upgrade: "this" visibility in getDefaultProps

I am upgrading some older react component I inherited (v0.10.0) to work with the latest version of react (v0.14.8).
The following scenario stopped working:
// within a react component
onClick: function() {
// DO SOMETHING
}
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: this.onClick
}
}
This is easily resolved moving the code into an anonymous function, like the following:
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: function() {
//DO SOMETHING
}
}
}
My question is: why has the visibility of 'this' changed at that level and what's the best way to refactor this code? And what if I had-to/wanted-to use 'this' at that level?
Any help appreciated, as a disclaimer I am a react super-beginner!
The result of getDefaultProps() is shared across all instances of a component. That means that the result can't rely on any particular instance of the component. The reason it changed is likely because of the performance benefit from caching, although I can't say for sure.
As for refactoring the code, I'm not sure there's a silver-bullet here. From my perspective what you currently have seems like an anti-pattern. Props are meant to be passed in by consumers that have no knowledge of the inner workings of the component, so it seems odd that a default value for a prop would depend on the inner workings. Without knowing exactly what you're doing, I would say your best bet is to just use null as the default value for the prop, then check the value at runtime when you do have access to the this context.
handleSomeAction() {
if (!this.props.onClick) {
// DO SOMETHING
}
}

2-way data binding in native web components

I've been reading up on web components and am pretty intrigued by the nascent spec. Does anyone know if there is any support for 2-way data binding in the DOM, without having to use Polymer? An example would be appreciated.
Object.observe is a potential new way to do databinding in javascript. This feature is scheduled for Ecmascript 7(javascript), but some browsers currently support it, check here. Also check out this html5rocks article on object.observe
No, data binding isn't part of the Web Components spec.
You can of course implement data binding yourself using native JavaScript event listeners, and possibly the Proxy object, but it's probably best not to re-invent the wheel: if you want data binding, choose one of the many JavaScript frameworks out there which supports that. Polymer, React, Angular, and Vue are some recent examples of such libraries.
I've been playing around with this over the last few days. You can create a StateObserver class, and extend your web components from that. A minimal implementation looks something like this:
// create a base class to handle state
class StateObserver extends HTMLElement {
constructor () {
super()
StateObserver.instances.push(this)
}
stateUpdate (update) {
StateObserver.lastState = StateObserver.state
StateObserver.state = update
StateObserver.instances.forEach((i) => {
if (!i.onStateUpdate) return
i.onStateUpdate(update, StateObserver.lastState)
})
}
}
StateObserver.instances = []
StateObserver.state = {}
StateObserver.lastState = {}
// create a web component which will react to state changes
class CustomReactive extends StateObserver {
onStateUpdate (state, lastState) {
if (state.someProp === lastState.someProp) return
this.innerHTML = `input is: ${state.someProp}`
}
}
customElements.define('custom-reactive', CustomReactive)
class CustomObserved extends StateObserver {
connectedCallback () {
this.querySelector('input').addEventListener('input', (e) => {
this.stateUpdate({ someProp: e.target.value })
})
}
}
customElements.define('custom-observed', CustomObserved)
<custom-observed>
<input>
</custom-observed>
<br />
<custom-reactive></custom-reactive>
fiddle here
I like this approach because it occurs directly between precisely those elements you want to communicate with, no dom traversal to find data- properties or whatever.
One way: $0.model = {data}; setter on $0 assigns $0.data, responding to the update, and the other way: $1.dispatchEvent(new CustomEvent('example', {detail: $1.data, cancelable: true, composed: true, bubbles: true})); with $0.addEventListenever('example', handler) gives 2 way data binding. The data object is the same, shared on 2 elements, events and setters allow responding to updates. To intercept updates to an object a Proxy works model = new Proxy(data, {set: function(data, key, value){ data[key] = value; ...respond... return true; }}) or other techniques. This addresses simple scenarios. You might also consider looking at and reading the source for Redux, it provides conventions that seem relatively popular. As Ajedi32 mentions reinventing the wheel for more complex scenarios is not so practical, unless it's an academic interest.

Can I force the increment value on scroll for dijit.form.NumberSpinner?

I'm using the dijit.form.NumberSpinner() widget in a form for looking up indexed content. This works great in most cases—entering a number, using the arrow keys, and using the spinner buttons all respond in the right manner. However, on some browsers (notably Firefox), using the scroll wheel over the field increments the value by something > 1.
Is there a way to force the scroll increment on such a number field to be 1 across all browsers? The +3/-3 behavior is strongly undesirable for my application as the results are scrolled through in real time as the value is updated.
I am already using a custom widget derived from NumberSpinner so adding or over-riding a property should not be difficult if that is what is required, I just don't know what to change. The docs only say the increment should be 1 for arrow keys, they don't say anything about scrolling.
That's because it depends on what the event itself provides (= given by the browser). Currently it uses either the evt.wheelDelta or evt.detail property from the mousewheel event to determine the increment value. However, there are no standards yet and most implementations are using certain functions to normalize the scrolling speed.
If you use the following code in Firefox:
require(["dojo/ready", "dojo/mouse", "dojo/on", "dijit/registry", "dijit/form/NumberSpinner", "dojo/parser"], function(ready, mouse, on, registry) {
ready(function() {
on(registry.byId("mySpinner").domNode, mouse.wheel, function(evt) {
console.log(evt.detail);
});
});
});
It will show you that this value is 3 or -3 when executed in Firefox.
If you really don't want it to depend on what the browser says, you can override the _mouseWheeled function:
FixedNumberSpinner = declare("dijit/form/FixedNumberSpinner", [ NumberSpinner ], {
_mouseWheeled: function(/*Event*/ evt){
evt.stopPropagation();
evt.preventDefault();
var wheelDelta = evt.wheelDelta > 0 ? 1 : -1;
var detailDelta = evt.detail > 0 ? -1 : 1;
var scrollAmount = evt.detail ? detailDelta : wheelDelta;
if(scrollAmount !== 0){
var node = this[(scrollAmount > 0 ? "upArrowNode" : "downArrowNode" )];
this._arrowPressed(node, scrollAmount, this.smallDelta);
if(this._wheelTimer){
this._wheelTimer.remove();
}
this._wheelTimer = this.defer(function(){
this._arrowReleased(node);
}, 50);
}
}
});
But please remember that the implementation might still change in the near future, so personally I would just stick with the increment of 3.
A full example can be found on JSFiddle: http://jsfiddle.net/4ZQTY/5/
EDIT: As mentioned in the comments, an even easier solution would be to override the adjust() function.
In most cases it is best to leave the behavior of such widgets alone. The mouse wheel action taken will be familiar to the users of each browser as the stock input widgets respond the same way.
In the event that over-riding this does make sense, you can tweak the adjust() method of the dijit widget. If you want to force the widget to step through every intermediate value no matter size adjustment was requested, you can force the delta value to be 1, then proceed with the contents of the original function.
adjust: function (val, delta) {
delta = delta > 0 ? 1 : -1;
return this.inherited(arguments);
}
(jsfiddle)
Thanks to Dimitri M's answer for putting me onto the hunt, but I found overriding the value in adjust() to be simpler than re-defining _mouseWheeled().

With ng-bind-html-unsafe removed, how do I inject HTML?

I'm trying to use $sanitize provider and the ng-bind-htm-unsafe directive to allow my controller to inject HTML into a DIV.
However, I can't get it to work.
<div ng-bind-html-unsafe="{{preview_data.preview.embed.html}}"></div>
I discovered that it is because it was removed from AngularJS (thanks).
But without ng-bind-html-unsafe, I get this error:
http://errors.angularjs.org/undefined/$sce/unsafe
Instead of declaring a function in your scope, as suggested by Alex, you can convert it to a simple filter :
angular.module('myApp')
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
Then you can use it like this :
<div ng-bind-html="preview_data.preview.embed.html | to_trusted"></div>
And here is a working example : http://jsfiddle.net/leeroy/6j4Lg/1/
You indicated that you're using Angular 1.2.0... as one of the other comments indicated, ng-bind-html-unsafe has been deprecated.
Instead, you'll want to do something like this:
<div ng-bind-html="preview_data.preview.embed.htmlSafe"></div>
In your controller, inject the $sce service, and mark the HTML as "trusted":
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.preview_data.preview.embed.htmlSafe =
$sce.trustAsHtml(preview_data.preview.embed.html);
}
Note that you'll want to be using 1.2.0-rc3 or newer. (They fixed a bug in rc3 that prevented "watchers" from working properly on trusted HTML.)
You need to make sure that sanitize.js is loaded. For example, load it from https://ajax.googleapis.com/ajax/libs/angularjs/[LAST_VERSION]/angular-sanitize.min.js
you need to include ngSanitize module on your app
eg: var app = angular.module('myApp', ['ngSanitize']);
you just need to bind with ng-bind-html the original html content. No need to do anything else in your controller. The parsing and conversion is automatically done by the ngBindHtml directive. (Read the How does it work section on this: $sce). So, in your case <div ng-bind-html="preview_data.preview.embed.html"></div> would do the work.
For me, the simplest and most flexible solution is:
<div ng-bind-html="to_trusted(preview_data.preview.embed.html)"></div>
And add function to your controller:
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
Don't forget add $sce to your controller's initialization.
The best solution to this in my opinion is this:
Create a custom filter which can be in a common.module.js file for example - used through out your app:
var app = angular.module('common.module', []);
// html filter (render text as html)
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}])
Usage:
<span ng-bind-html="yourDataValue | html"></span>
Now - I don't see why the directive ng-bind-html does not trustAsHtml as part of its function - seems a bit daft to me that it doesn't
Anyway - that's the way I do it - 67% of the time, it works ever time.
You can create your own simple unsafe html binding, of course if you use user input it could be a security risk.
App.directive('simpleHtml', function() {
return function(scope, element, attr) {
scope.$watch(attr.simpleHtml, function (value) {
element.html(scope.$eval(attr.simpleHtml));
})
};
})
You do not need to use {{ }} inside of ng-bind-html-unsafe:
<div ng-bind-html-unsafe="preview_data.preview.embed.html"></div>
Here's an example: http://plnkr.co/edit/R7JmGIo4xcJoBc1v4iki?p=preview
The {{ }} operator is essentially just a shorthand for ng-bind, so what you were trying amounts to a binding inside a binding, which doesn't work.
I've had a similar problem. Still couldn't get content from my markdown files hosted on github.
After setting up a whitelist (with added github domain) to the $sceDelegateProvider in app.js it worked like a charm.
Description: Using a whitelist instead of wrapping as trusted if you load content from a different urls.
Docs: $sceDelegateProvider and ngInclude (for fetching, compiling and including external HTML fragment)
Strict Contextual Escaping can be disabled entirely, allowing you to inject html using ng-html-bind. This is an unsafe option, but helpful when testing.
Example from the AngularJS documentation on $sce:
angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
// Completely disable SCE. For demonstration purposes only!
// Do not use in new projects.
$sceProvider.enabled(false);
});
Attaching the above config section to your app will allow you inject html into ng-html-bind, but as the doc remarks:
SCE gives you a lot of security benefits for little coding overhead.
It will be much harder to take an SCE disabled application and either
secure it on your own or enable SCE at a later stage. It might make
sense to disable SCE for cases where you have a lot of existing code
that was written before SCE was introduced and you're migrating them a
module at a time.
You can use filter like this
angular.module('app').filter('trustAs', ['$sce',
function($sce) {
return function (input, type) {
if (typeof input === "string") {
return $sce.trustAs(type || 'html', input);
}
console.log("trustAs filter. Error. input isn't a string");
return "";
};
}
]);
usage
<div ng-bind-html="myData | trustAs"></div>
it can be used for other resource types, for example source link for iframes and other types declared here