I have created a custom package in Meteor adding javascript to the application.
My package.js:
Package.describe({
name: 'markel:theme',
version: '1.0.0',
summary: 'Theme package',
});
Package.onUse(function(api) {
// Import all JS files required by the template
api.addFiles(['assets/js/custom.js']);
});
In custom.js:
function theme_test() {
console.log('Theme test');
}
When meteor loads the package in the application, it places the function in IIFE. So the javascript is in (function(){here}). So my function will return undefined.
How can I define that function and use it?
I hope one of these options will solve your issue, as I found it hard to reproduce any undefined values.
Option 1 - Use modules
While you can auto-add files via api.addFiles you can optionally still export them explicitly:
package.js
Package.describe({
summary: 'Theming',
version: '1.0.0',
name: 'marcelweidum:theme',
git: 'https://github.com/MarcelWeidum/stack-question.git'
});
Package.onUse((api) => {
api.use('ecmascript') // required to use import/export
api.addFiles([
'js/custom.js'
], 'client');
api.mainModule('main.js')
});
package/js/custom.js
export const theme_test = function theme_test () {
console.log('Here am I!');
}
console.log('Loaded');
package/main.js (in root folder of the package)
export { theme_test } from './js/custom'
client/main.js
import { theme_test } from 'meteor/marcelweidum:theme'
theme_test()
will give you on the console:
Loaded
Here am I!
Option 2 - use api.export
You can export the theme using an implicit global that is made accessible immediately using api.export:
package.js
Package.describe({
summary: 'Theming',
version: '1.0.0',
name: 'marcelweidum:theme',
git: 'https://github.com/MarcelWeidum/stack-question.git'
});
Package.onUse((api) => {
api.addFiles([
'js/custom.js'
], 'client');
api.export('MyTheme')
});
*package/js/custom.js*
function theme_test () {
console.log('Here am I!');
}
MyTheme = MyTheme || {}
MyTheme.theme_test = theme_test
console.log('Loaded');
*client/main.js*
```javascript
MyTheme.theme_test()
will give you on the console:
Loaded
Here am I!
Option 3 - Import file explicitly
This will hover load the file's content only at the point it's imported
package.js
Package.describe({
summary: 'Theming',
version: '1.0.0',
name: 'marcelweidum:theme',
git: 'https://github.com/MarcelWeidum/stack-question.git'
})
Package.onUse((api) => {
api.use('ecmascript')
})
*js/custom.js* (in root folder of the package)
```javascript
export const theme_test = function theme_test () {
console.log('Here am I!');
}
console.log('Loaded');
package/js/custom.js
export const theme_test = function theme_test () {
console.log('Here am I!');
}
console.log('Loaded');
client/main.js
import { theme_test } from 'meteor/marcelweidum:theme/js/custom'
theme_test()
will give you on the console:
Loaded
Here am I!
Option 4 use bare option, in case the file is used by a compiler plugin:
If you don't want the file to be wrapped inside a closure, for example because it will be used by a custom compier plugin, then add the bare option:
Package.describe({
summary: 'Theming',
version: '1.0.0',
name: 'marcelweidum:theme',
git: 'https://github.com/MarcelWeidum/stack-question.git'
});
Package.onUse((api) => {
api.addFiles([
'js/custom.js'
], 'client', { bare: true });
});
This will still load the file but you will have to use your isobuild plugin to handle the file.
Related
Strapi Version: 4.3.0
Operating System: Ubuntu 20.04
Database: SQLite
Node Version: 16.16
NPM Version: 8.11.0
Yarn Version: 1.22.19
I have created Preview button for an article collection type. I'm using the Strapi blog template. I managed to make the Preview button appear in the Content Manager. I hard coded the link to be opened when you click the Preview button and it works. Now, I want the plugin to use a link with environment variables instead of a hard coded link. I don't know how I can access the environment variables in the source code for the plugin.
My objective:
I want to replace
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
with
href={${CLIENT_FRONTEND_URL}?secret=${CLIENT_SECRET}&slug=${initialData.slug}`}
in ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
where CLIENT_FRONTEND_URL and CLIENT_SECRET are environment variables declared like so in .env:
CLIENT_FRONTEND_URL=http://localhost:3000
CLIENT_PREVIEW_SECRET=abc
Here's a rundown of the code I used:
First, I created a strapi app using the blog template, then created a plugin.
// Create strapi app named backend with a blog template
$ yarn create strapi-app backend --quickstart --template #strapi/template-blog#1.0.0 blog && cd backend
// Create plugin
$ yarn strapi generate
Next, I created a PreviewLink file to provide a link for the Preview button
// ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
import React from 'react';
import { useCMEditViewDataManager } from '#strapi/helper-plugin';
import Eye from '#strapi/icons/Eye';
import { LinkButton } from '#strapi/design-system/LinkButton';
const PreviewLink = () => {
const {initialData} = useCMEditViewDataManager();
if (!initialData.slug) {
return null;
}
return (
<LinkButton
size="S"
startIcon={<Eye/>}
style={{width: '100%'}}
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
variant="secondary"
target="_blank"
rel="noopener noreferrer"
title="page preview"
>Preview
</LinkButton>
);
};
export default PreviewLink;
Then I edited this pregenerated file in the bootstrap(app) { ... } section only
// ./src/plugins/previewbtn/admin/src/index.js
import { prefixPluginTranslations } from '#strapi/helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import Initializer from './components/Initializer';
import PreviewLink from './components/PreviewLink';
import PluginIcon from './components/PluginIcon';
const name = pluginPkg.strapi.name;
export default {
register(app) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: name,
},
Component: async () => {
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
return component;
},
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugin::plugin-name.actionType
// subject: null,
// },
],
});
app.registerPlugin({
id: pluginId,
initializer: Initializer,
isReady: false,
name,
});
},
bootstrap(app) {
app.injectContentManagerComponent('editView', 'right-links', {
name: 'preview-link',
Component: PreviewLink
});
},
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map(locale => {
return import(
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);
return Promise.resolve(importedTrads);
},
};
And lastly this created this file to enable the plugin Reference
// ./config/plugins.js
module.exports = {
// ...
'preview-btn': {
enabled: true,
resolve: './src/plugins/previewbtn' // path to plugin folder
},
// ...
}
I solved this by adding a custom webpack configuration to enable Strapi's admin frontend to access the environment variables as global variables.
I renamed ./src/admin/webpack.example.config.js to ./src/admin/webpack.config.js. Refer to the v4 code migration: Updating the webpack configuration from the Official Strapi v4 Documentation.
I then inserted the following code, with help from Official webpack docs: DefinePlugin | webpack :
// ./src/admin/webpack.config.js
'use strict';
/* eslint-disable no-unused-vars */
module.exports = (config, webpack) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
config.plugins.push(
new webpack.DefinePlugin({
CLIENT_FRONTEND_URL: JSON.stringify(process.env.CLIENT_FRONTEND_URL),
CLIENT_PREVIEW_SECRET: JSON.stringify(process.env.CLIENT_PREVIEW_SECRET),
})
)
return config;
};
I rebuilt my app afterwards and it worked.
You shouldn't have to change the webpack config just find .env file in the root directory
add
AWS_ACCESS_KEY_ID = your key here
then just import by
accessKeyId: env('AWS_ACCESS_KEY_ID')
I am using vueHtml2Pdf to generate my page to pdf, but when I wrap my content inside VueHtml2pdf tag nothing renders on my page, but it downloads when I click the download button. (Nuxt)
methods: {
downloadPDF() {
this.$refs.html2Pdf.generatePdf()
},
},
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<ArticleActions #download="downloadPDF()" />
<client-only>
<vue-html2pdf
ref="html2Pdf"
:show-layout="false"
:enable-download="true"
:pdf-quality="2"
:manual-pagination="true"
pdf-content-width="100%"
:html-to-pdf-options="htmlToPdfOptions"
>
<section slot="pdf-content">
<!-- content -->
<div
v-interpolation="{ newWindow: true }"
class="articleContent__content"
v-html="article.content"
></div>
<!-- /content -->
</section>
</vue-html2pdf>
</client-only>
I have a working solution for Nuxtv3 (with server-side rendering). After trying a bunch of different vue-specific packages, including vue-html2pdf, I realized that most of them have been written for Vue2.
Instead, I chose to use html2pdf directly.
Upon directly importing html2pdf in the component where I need to add the functionality for converting html to pdf, Nuxt throws the following error: ReferenceError: self is not defined. This essentially happens because the line where the library is being imported runs on the server side as well and when it is imported, it tries to access a variable that isn't defined on the server side.
My solution was to create a custom plugin that runs only on the client side. It is very simple to do this in Nuxtv3 by just ending the filename with .client.ts as opposed to just .ts. Here is what plugins/html2pdf.client.ts looks like:
import html2pdf from 'html2pdf.js'
export default defineNuxtPlugin(() => {
// had to make a plugin because directly importing html2pdf.js in the component
// where I need to use it was causing an error as the import would run on the server
// side and html2pdf.js is a client-side-only library. This plugin runs on the
// client side only (due to the .client extension) so it works fine.
return {
provide: {
html2pdf: (element, options) => {
return html2pdf(element, options)
}
}
}
})
Now, I can safely use it in my component as:
const { $html2pdf } = useNuxtApp()
function downloadPDF() {
if (document) {
const element = document.getElementById('html2pdf')
// clone the element: https://stackoverflow.com/questions/60557116/html2pdf-wont-print-hidden-div-after-unhiding-it/60558415#60558415
const clonedElement = element.cloneNode(true) as HTMLElement
clonedElement.classList.remove('hidden')
clonedElement.classList.add('block')
// need to append to the document, otherwise the downloading doesn't start
document.body.appendChild(clonedElement)
// https://www.npmjs.com/package/html2pdf.js/v/0.9.0#options
$html2pdf(clonedElement, {
filename: 'filename.pdf',
image: { type: 'png' },
enableLinks: true
})
clonedElement.remove()
}
}
Basic usage of html2pdf: https://www.npmjs.com/package/html2pdf.js/v/0.9.0#usage
Configuration for html2pdf: https://www.npmjs.com/package/html2pdf.js/v/0.9.0#options
If someone looking for how to use html2pdf in nuxt 2.
install html2pdf.js using npm or yarn
create html-to-pdf.js file in nuxt plugin directory with below code
import html2pdf from 'html2pdf.js'
export default (context, inject) => {
inject('html2pdf', html2pdf)
}
Then add plugin to nuxt.config.js
plugins: [
'#/plugins/axios',
......
{ src: '#/plugins/html-to-pdf', mode: 'client' },
],
How to use , example in your component menthod
methods: {
export() {
this.$html2pdf(this.$refs.document, {
margin: 1,
filename: 'file-name.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 1,
dpi: 192,
letterRendering: true,
ignoreElements: true,
},
jsPDF: { unit: 'pt', format: 'a2', orientation: 'portrait' },
})
},
main.js has this code
window.l = function () { }
try {
window.l = console.log.bind(console)
} catch (e) { }
which works in non-Vue apps. However, when calling
l("test")
from a Vue action/method, it complains it isn't defined.
How can that work?
Reasoning: need to output some debugging data, with as less typing as possible.
When you want to add global-level functionalities to Vue, you should generally use mixins or plugins.
For the next examples, I assume you are using vue-cli with the complete webpack template. Moreover, we will use App.vue as a practical reference, but you can apply the same principles to other components...
Mixins
Create a mixin named log.js (in a mixins folder) with the following code:
export default {
methods: {
l (...args) { // rest parameters
console.log(...args) // spread operator
}
}
}
Open App.vue, import your mixin and use it:
<script>
import log from './mixins/log'
export default {
name: 'app',
mixins: [log],
created () {
this.l('Foo', 'Bar') // Foo Bar
}
}
</script>
Plugins
Create a plugin named log.js (in a plugins folder) with the following code:
export default {
install (Vue, options) {
Vue.prototype.$l = console.log.bind(console)
Vue.l = console.log.bind(console)
}
}
Open your main.js and declare your global plugin:
import log from './plugins/log'
Vue.use(log)
Open App.vue, import Vue and use your plugin:
<script>
import Vue from 'vue'
export default {
name: 'app',
created () {
this.$l('Foo') // Foo
Vue.l('Bar') // Bar
}
}
</script>
You might say: "Hey, why should I have to write this or Vue? I just wanna write l, that's all!". Well... This is actually how Vue has been designed. In order to provide global functionalities (shared by all components), you have to add static properties to the Vue object or prototype properties (Vue.prototype) that are accessible through this in Vue instances.
EDIT
I have just thought about an alternative solution...
You can edit your index.html to add this:
<script>
var l = console.log.bind(console)
</script>
Then, to avoid ESLint errors, you should also edit your .eslintrc.js file to reference your new global variable:
globals: {
l: true
}
The file looks like this:
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
globals: {
l: true
},
env: {
browser: true,
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
Restart your dev server. Now you should be able to use l in your code:
<script>
export default {
name: 'app',
created () {
l('It works!')
}
}
</script>
Assign console.log like this.
window.l=console.log;
There is a tagging library here: https://github.com/Tealium/integration-angularjs/blob/master/tealium_angular.js .
We integrated it into our application. During app initialisation we need to provide some configuration for this library. This is done like this:
Our app.js:
angular.module('appname', [ 'TealiumHelper' ])
.config(function (tealiumProvider) {
tealiumProvider.setConfig({
account: 'accountxx',
profile: 'profilexx',
environment: 'dev'
});
})
There is a karma test similar to this:
(function () {
'use strict';
describe('controllertest', function () {
beforeEach(module('appname','TealiumHelper'));
it('bla', function () {
//test code
}
}
}
When I start the test, I get the following error coming from tealium_angular.js:
"account or profile value not set. Please configure Tealium first"
How can I set these config values in my karma test?
In the test you can provide your own implementation for TealiumHelper module like
describe('controllertest', function () {
beforeEach(module('appname'))
angular.module('TealiumHelper', []).provider('tealium', {
$get: function () {},
setConfig: function () {}
});
/*** test starts here ***/
})
The solution was (my colleague Andrea Fűrész fixed it actually):
She created a js file with the following content:
function TealiumConfig() {
'use strict';
module('TealiumHelper', function (tealiumProvider) {
tealiumProvider.setConfig({
account: 'foooooo',
profile: 'baaaar',
environment: 'dev'
})
});
}
Then in karma config it was added into the "files" configuration. Then it worked.
I have setup an angular2 / Electron app similar to the explanation in this video : https://www.youtube.com/watch?v=pLPCuFFeKOU. The project I am basing my code on can be found here : https://github.com/rajayogan/angular2-desktop
I am getting the error:
app.ts:16Uncaught TypeError: Cannot read property 'on' of undefined
When I try to run this code:
import { bootstrap } from '#angular/platform-browser-dynamic';
import { Component } from '#angular/core';
import { MenuComponent} from './menu';
import { ConfigEditorComponent } from './config-editor';
import { remote, ipcRenderer} from 'electron';
let {dialog} = remote;
//Functions used for select server xml callbacks.
const ipc = require('electron').ipcMain
const xml2js = require('xml2js')
const fs = require('fs')
var parser = new xml2js.Parser();
ipc.on('open-file-dialog', function (event) {
dialog.showOpenDialog({
title:"Select zOS Connect server.xml",
properties: ['openFile', 'openDirectory'],
filters: [
{name: 'XML', extensions: ['xml']},
{name: 'All Files', extensions: ['*']}
]
}, function (files) {
if (files){
fs.readFile(files[0], function(err, data) {
parser.parseString(data, function (err, result) {
console.dir(result);
process_server_xml(event,result);
})
})
}
})
})
function process_server_xml(event,json){
console.log("oh hello!")
event.sender.send('selected-directory', json)
console.log("oh im done!")
}
#Component({
selector: 'connect-toolkit',
templateUrl: 'app.component.html',
directives: [ MenuComponent, ConfigEditorComponent ]
})
export class AppComponent {
constructor() {
var menu = remote.Menu.buildFromTemplate([{
label: 'Raja',
submenu: [
{
label: 'open',
click: function(){
dialog.showOpenDialog((cb) => {
})
}
},
{
label: 'opencustom',
click: function(){
ipcRenderer.send('open-custom');
let notification = new Notification('Customdialog', {
body: 'This is a custom window created by us'
})
}
}
]
}])
remote.Menu.setApplicationMenu(menu);
}
}
bootstrap(AppComponent);
I think the problem may be:
const ipc = require('electron').ipcMain
const xml2js = require('xml2js')
const fs = require('fs')
var parser = new xml2js.Parser();
Is it possible require doesn't work here, and somehow I need to use import statements instead from my ts files? If this is the case how do I use the import in order to get the ipcMain object and my xml2js etc?
Why would that be the case? How can I make require work within the ts files if this is the problem.
Note that if I remove the require lines, and all the ipc.on code everything runs as expected and works fine (other than the fact that the ipc event is never received ;)
Calling ipcMain doesn't work because you're not on main (i.e., the electron side code, which is on electron index.js file), your are on renderer (web page). Therefore you must use ipcRenderer instead, which is already imported using es6 import syntax on top of your app.ts file. And if you want to make something using electron ipcMain, it have to be done from the electron code side.
import {remote, ipcRenderer} from 'electron';
Electron ipc notes:
ipcMain Communicate asynchronously from the main process to renderer processes.
ipcRenderer Communicate asynchronously from a renderer process to the main process.