How to dynamically push new script to Nuxt pages at runtime? - javascript

In Nuxt v2 we could add some scripts to an page like this:
head() {
return {
title: this.pageTitle,
script: [
{
src: this.scriptSrc,
type: 'text/javascript',
defer: true,
callback: () => {
this.setScriptLoaded(true)
}
}
]
}
}
But as you can see it's hard coded.
I need to add a new script at runtime under especial circumstances (e.g. if user click on a button.)
I know that we could just use Vanilla JS to add scripts dynamically like this:
const script = document.createElement('script')
script.setAttribute('src', this.scriptSrc)
document.head.appendChild(script)
But I wonder if there is a Nuxt solution?

Related

Wordpress custom block not showing in block inserter

I'm trying to create a custom block for a site but the block is not appearing in the editor dialogue. I've gone through multiple tutorials and changed my code a lot but it simply won't work.
What I've checked:
The block is added through a plugin but it also doesn't work when
moved to the theme.
I know the plugin is working correctly as I can use other wp
hooks/actions with no issues within the plugin.
I have tried using both 'init' & 'enqueue_block_assets' but neither
work.
I have verified all the file locations and paths are correct as I
have echoed them out to check.
I have changed to the default theme and it still does not appear.
Any help would be appreciated.
Here is the js block src (which is compiled):
import { registerBlockType } from '#wordpress/blocks'
registerBlockType('ghs/landing-page-block', {
title: 'Landing Page',
apiVersion: 2,
category: 'design',
icon: 'smiley',
description: 'Layout for the GHS landing page',
keywords: ['GHS', 'landing', 'page', 'front'],
edit: () => {
return (<div>hello</div>)
},
save: () => {
return (<div>hello</div>)
}
});
and the php registering it:
add_action('init', function() {
$asset_file = include( WP_PLUGIN_DIR . '/ghs-custom-blocks/assets/js/landing-page-block.asset.php');
wp_register_script('ghs-landing-page',
WP_PLUGIN_DIR . '/ghs-custom-blocks/assets/js/landing-page-block.js',
$asset_file['dependencies'],
$asset_file['version']);
register_block_type('ghs/landing-page-block', [
'api_version' => 2,
'editor_script' => 'ghs-landing-page',
]);
});
Solved it, it was because I was using WP_PLUGIN_DIR instead of plugin_dir_url(__FILE__). It meant the js request url was from the root instead of the wp installation.

How can I get a value from index.html and use it to render a second template after parsing JSON file?

I'm trying to wrap my head around gulp and modern JavaScript bundling.
The application is pretty simple:
A html page is displayed to the user, where he must check a few radio buttons.
User then submits his selections clicking on a button.
Upon choice submit, a JSON file must be parsed, in order to display one amongst 20 possible html pages
I have the following (simplified) structure:
dist/
index.html
src/
scripts/
main.js
modules/
dispatchForm.js
styles/
index.scss
templates/
index.pug
partials/
default.pug
selectors.pug
gulpfile.js
data.json
In my gulpfile.js, I have:
const bundler = () => {
return rollup({
input: './src/scripts/main.js',
plugins: [
babel(pkg.babel),
nodeResolve(),
commonJS(),
],
}).then((bundle) => bundle.write({
file: '.tmp/bundle.js',
format: 'umd',
sourceMap: 'inline',
}));
};
const uglify = () => {
return src('.tmp/bundle.js', {sourcemaps: true})
.pipe(plugins.uglify())
.pipe(dest('.tmp'))
.pipe(plugins.size({title: 'bundler minified'}));
};
const styles = () => {
const AUTOPREFIXER_BROWSERS = [
'ie >= 10',
'ie_mob >= 10',
'ff >= 30',
'chrome >= 34',
'safari >= 7',
'opera >= 23',
'ios >= 7',
'android >= 4.4',
'bb >= 10',
];
return src([
'src/styles/main.scss',
'src/styles/**/*.css',
])
.pipe(plugins.sassGlob())
.pipe(plugins.sass({
precision: 10,
}).on('error', plugins.sass.logError))
.pipe(plugins.autoprefixer(AUTOPREFIXER_BROWSERS))
.pipe(dest('.tmp'))
.pipe(plugins.if('*.css', plugins.cssnano()))
.pipe(dest('.tmp'))
.pipe(plugins.size({title: 'styles'}));
};
// Uses PUG as template
const templates = (env) => () => {
return src('./src/templates/*.pug')
.pipe(plugins.pug({locals: {
title: pkg.title,
description: pkg.description,
env,
}}))
.pipe(dest('dist'))
.pipe(plugins.size({title: 'templates'}));
};
const reload = (done) => {
server.reload();
return done();
};
const images = (env) => () => {
const destination = env === 'deploy' ? 'dist' : '.tmp';
return src('./src/images/**/*.{gif,jpg,png,svg}')
.pipe(dest(destination))
.pipe(plugins.size({title: 'size'}))
};
const serve = () => {
server.init({
notify: false,
logPrefix: 'WSK',
scrollElementMapping: ['main', '.mdl-layout'],
server: ['.tmp', 'dist'],
port: 3000,
});
watch(
['src/**/*.pug'],
series(templates('development'), reload)
);
watch(
['src/styles/**/*.{scss,css}'],
series(styles, templates('development'), reload)
);
watch(
['src/scripts/main.js', 'src/scripts/**/*.js'],
series(bundler, templates('development'), reload)
);
watch(
['src/images/**/*.{gif,jpg,png,svg}'],
series(images('development'), templates('development'), reload)
);
};
const clean = () => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true});
exports.default = series(
clean,
bundler,
uglify,
styles,
templates('deploy'),
images('deploy')
);
exports.serve = series(
bundler,
styles,
templates('development'),
images('development'),
serve
);
The way I understand it, after cleaning the files, the bundler will:
Serve a html page in dist folder after compiling from my sources main.js, dispatchForm.js, scss and pug templates.
Main.js
import dispatchForm from './modules/dispatchForm';
const domContentLoad = (fn) => {
if (document.readyState !== 'loading') fn();
else document.addEventListener('DOMContentLoaded', fn);
};
domContentLoad(() => {
dispatchForm();
});
dispatchForm.js
const button = document.querySelector('[data-calculator-button]');
function updateValue() {
const gain = document.querySelector('[data-calculator-form][name="gain"]:checked');
const cost = document.querySelector('[data-calculator-form][name="cost"]:checked');
if (gain && cost) {
button.removeAttribute('disabled');
button.classList.remove('selectors__button--inactive');
} else {
button.setAttribute('disabled', '');
button.classList.add('selectors__button--inactive');
}
}
function dispatchForm() {
const radioInput = document.querySelectorAll('[data-calculator-form]');
radioInput.forEach(element => element.addEventListener('input', updateValue));
}
export default dispatchForm;
selectors.pug
...
.selectors__form
.selectors__radio
input.selectors__input(type='radio' id='gain-points' name='gain' value='points' data-calculator-form)
label.selectors__label(for='gain-points')
.selectors__radio
input.selectors__input(type='radio' id='gain-money' name='gain' value='money' data-calculator-form)
label.selectors__label(for='gain-money')
.selectors__form
for value in [70, 80, 90, 100, 110, 120, 130]
.selectors__radio
input.selectors__input(type='radio' id=`have-${value}` name='cost' value=value data-calculator-form)
label.selectors__label(for=`have-${value}`)
| Até
b= ` C$${value}`
button.selectors__button.selectors__button--calculate.selectors__button--inactive(disabled=true data-calculator-button)
...
The above creates some selectors for 'cost' or 'gain' from selectors.pug, main.js and dispatchForm.js, via gulps 'bundler', and renders it as html.
But now I would like to:
Use one of the two button submitted values (${value}) and pass it as an argument to a function that will parse a JSON file.
Finally, the parsed JSON result will be passed to another pug file
Questions
How do I get this JSON (from dispatchForm.js? from gulpfile.js? from pug natively?) and pass it to another pug template?
Should JSON fetching be dealt with on separate JS module, since displayed JSON will be rendered on a separate html page, using another pug template partial? How so?
Should all possible second page html files be generated at build time and JS be used to display only the one based on the submitted value? Or should this second html page be rendered dinamically?
gulp-data
I also learned that there are packages like gulp-data, used to handle json data, and I don't know if they are the way to go here.
Also, this SO question hints as how to pass pug JSON objects to client side JavaScript.
The way you've phrased this question makes me think your main issue is conflating the build step with the application "runtime" (as in, when users are using your application), like when dispatchForm.js would ever be run. Gulp is a tool to generate out your project, but this is meant to happen way before any users are interacting with your site.
The SO question you linked is having express render the pug pages at "runtime", which, architecturally, is pretty different from having it happen at the build step with gulp.
If I'm understanding what you want correctly, off the top of my head there's 3 main ways to do it. The first would be by having the client-side JS manipulate the dom and change the page appropriately. You could use pug for this, rendered into a JS library via something like rollup-plugin-pug (found via this SO answer).
The second would be having this be an api call to a server, which then renders a pug template (which is what the SO question you linked is doing).
Or third, you could do something like rendering out all possible pages you'd want your users to go to at build time, and just make the dispatchForm.js just be sending them to the appropriate pages. In this case I'd recommend defining out these options in one place (in say, a json file) and having that be the source of a gulp pipeline. It gets a little zany having gulp generate multiple files from a single file, but you can find a variety of ways people have done something similar like this github thread, this Stack Overflow answer, this gist, etc.
Edit
If you want stuff to happen "upon choice submit", that's way 1. So using rollup-plugin-pug, it would look something like (completely untested and off the top of my head):
//- template.pug
.content
p= gain
p= cost
// modules/calculateButton.js
import templateFn from '../../templates/partials/template.pug';
const button = document.querySelector('[data-calculator-button]');
button.addEventListener('click', (e) => {
if (e.target.getAttribute('disabled')) return;
const gain = document.querySelector('[data-calculator-form][name="gain"]:checked').value;
const cost = document.querySelector('[data-calculator-form][name="cost"]:checked').value;
document.getElementById("result").innerHTML = templateFn({
gain, cost
});
});
Otherwise nothing's parsed on submit. For examples of the 3rd way, I'd recommend checking out the links I sent above. A lot of build scripts is just finding your own way to do things that has the right amount of power/complexity balanced with ease of maintenance.

How can I get dynamic data from within gatsby-config.js?

Consider the following code within gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: `${dynamicURL}`, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
}
As you can see above, the URL for the source plugin is something that I need to be dynamic. The reason for this is that the file URL will change every time it's updated in the CMS. I need to query the CMS for that field and get its CDN URL before passing to the plugin.
I tried adding the following to the top of gatsby-config.js but I'm getting errors.
const axios = require("axios")
let dynamicURL = ""
const getBrands = async () => {
return await axios({
method: "get",
url: "https://some-proxy-url-that-returns-json-with-the-csv-file-url",
})
}
;(async () => {
const brands = await getBrands()
dynamicURL = brands.data.summary.url
})()
I'm assuming this doesn't work because the config is not waiting for the request above to resolve and therefore, all we get is a blank URL.
Is there any better way to do this? I can't simply supply the source plugin with a fixed/known URL ahead of time.
Any help greatly appreciated. I'm normally a Vue.js guy but having to work with React/Gatsby and so I'm not entirely familiar with it.
I had similar requirement where I need to set siteId of gatsby-plugin-matomo dynamically by fetching data from async api. After searching a lot of documentation of gatsby build lifecycle, I found a solution.
Here is my approach -
gatsby-config.js
module.exports = {
siteMetadata: {
...
},
plugins: {
{
resolve: 'gatsby-plugin-matomo',
options: {
siteId: '',
matomoUrl: 'MATOMO_URL',
siteUrl: 'GATSBY_SITE_URL',
dev: true
}
}
}
};
Here siteId is blank because I need to put it dynamically.
gatsby-node.js
exports.onPreInit = async ({ actions, store }) => {
const { setPluginStatus } = actions;
const state = store.getState();
const plugin = state.flattenedPlugins.find(plugin => plugin.name === "gatsby-plugin-matomo");
if (plugin) {
const matomo_site_id = await fetchMatomoSiteId('API_ENDPOINT_URL');
plugin.pluginOptions = {...plugin.pluginOptions, ...{ siteId: matomo_site_id }};
setPluginStatus({ pluginOptions: plugin.pluginOptions }, plugin);
}
};
exports.createPages = async function createPages({ actions, graphql }) {
/* Create page code */
};
onPreInit is a gatsby lifecycle method which is executing just after plugin loaded from config. onPreInit lifecycle hook has some built in methods.
store is the redux store where gatsby is storing all required information for build process.
setPluginStatus is a redux action by which plugin data can be modified in redux store of gatsby.
Here the important thing is onPreInit lifecycle hook has to be called in async way.
Hope this helps someone in future.
Another approach that may work for you is using environment variables as you said, the URL is known so, you can add them in a .env file rather than a CSV.
By default, Gatsby uses .env.development for gatsby develop and a .env.production for gatsby build command. So you will need to create two files in the root of your project.
In your .env (both and .env.development and .env.production) just add:
DYNAMIC_URL: https://yourUrl.com
Since your gatsby-config.js is rendered in your Node server, you don't need to prefix them by GATSBY_ as the ones rendered in the client-side needs. So, in your gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: process.env.DYNAMIC_URL, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
It's important to avoid tracking those files in your Git repository since you don't want to expose this type of data.

autoComplete.js - how to resolve autoComplete is not defined

I want to use autocomplete.js in my application.
I have installed the package using yarn add #tarekraafat/autocomplete.js. I am using webpack 4.28 to bundle the javascript files and have require("#tarekraafat/autocomplete.js/dist/js/autoComplete"); to import the package into the application and placed the bundled file at the bottom before the closing BODY tag.
In my custom.js file, I am creating a new instance of autoComplete as follows:
new autoComplete({
data: {
src: async () => {
document.querySelector("#autoComplete_results_list").style.display = "none";
document.querySelector("#autoComplete").setAttribute("placeholder", "Loading...");
const source = await fetch("/employee/search");
const data = await source.json();
return data;
},
key: "name"
},
selector: "#autoComplete",
placeHolder: "type employee name to search...",
threshold: 0,
searchEngine: "strict",
highlight: true,
dataAttribute: { tag: "value", value: "" },
maxResults: Infinity,
renderResults: {
destination: document.querySelector("#autoComplete"),
position: "afterend"
},
onSelection: feedback => {
document.querySelector(".selection").innerHTML = feedback.selection.food;
document
.querySelector("#autoComplete")
.setAttribute("placeholder", `${event.target.closest(".autoComplete_result").id}`);
console.log(feedback);
}
});
However, on running the application, I am getting an error Uncaught ReferenceError: autoComplete is not defined with a reference to the location where I am creating the new instance.
I have read the getting started documentation and looked at the demo code and I can't figure out what I am missing. How do I resolve the error?
Your problem is in your import, you are not import the autoComplete correctly, so when you using the new autoComplete you are having error.
Change the require("#tarekraafat/autocomplete.js/dist/js/autoComplete"); to import autoComplete from '#tarekraafat/autocomplete.js';, put this on top of your file, right after jquery or something
Write your code inside
$(document).ready(function(){
// Write your Code Here
});

react init app only after calling function in html file

I am currently building app in React.js that is suppose to be a advanced widget. Main requirement is that app should initialise only when at some point in time someone will execute function:
startApp({
someData: 'test',
wrap: 'domSelector'
});
i found this article Link, where author claims you can do this:
<div id="MyApp"></div>
<script>
window.runReactApplication({
user: { name: 'BTM', email: 'example#example.com' }
}, document.querySelector('#MyApp'));
</script>
and index.js
function runApplication(data, node) {
ReactDOM.render(<App data={data} />, node);
}
window.runReactApplication = runApplication;
It doesnt work for me as i get runReactApplication is undefined error.
Just out of curiosity i tried to wrap execution around timeout and it worked:
<script>
setTimeout(function() {
window.runReactApplication({
user: { name: 'BTM', email: 'example#example.com' }
}, document.querySelector('#MyApp'));
}, 150);
</script>
i can guess that in production its not a big problem because i can include app code first and then script so i have guarantee that function is added to the window.
But in development environment its a problem i dont know how to solve, and having timeout around function is obviously crazy. Anyone has an idea ?
You can use dynamic imports instead of setTimeout.
Let's say all of your react logic is bundled inside of index.js. Simply do the following to ensure that index.js has been downloaded and run before the desired DOM attachment:
import('index.js').then(() => {
window.runReactApplication({
user: { name: 'BTM', email: 'example#example.com' }
}, document.querySelector('#MyApp'));
}
If you want to preload the index.js file for optimal speed, simply add
<script src="index.js"></script>
to the head of your index.html file.
In the broader context, however, I am not sure why your startApp function wouldn't simply be:
function startApp(params){
const {data, selector} = params;
ReactDOM.render({
<App data={data} />
}, document.querySelector(selector));
}
And you could just bind this as a callback to whatever html event you want (i.e., onClick for a button, onLoad for the document loading, etc.).
button.onclick = startApp({data:{email: me#test.com}, selector: 'domSelector'});

Categories