Reference Custom Product template - bigcommerce

According to the documentation, https://developer.bigcommerce.com/stencil-docs/development-quickstart/rendering-html-with-ajax, I can create a custom template based on a component file, such as products/product-view
What is the base page that contains the front-matter for rendering these components?
The component I am trying to render is not called from any html page using {{ > component/products/quicker-page }}. This seems to mean that it will not be included in the ./manifest.json, and is the root cause of the error displayed in stencil-cli when calling the getPage API.
window.stencilUtils.api.getPage('/product-1/', {
"template": "products/quicker-view"
},
(err, content) => {
console.log(content);
});
Error
TypeError: Uncaught error: Cannot read property 'components/products/quicker-view' of undefined
at TemplateAssembler.assemble (C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\server\plugins\renderer\renderer.module.js:466:26)
at getTemplatePaths (C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\lib\template-assembler.js:28:20)
at Async.each.err (C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\lib\template-assembler.js:93:20)
at C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\node_modules\async\dist\async.js:473:16
at iteratorCallback (C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\node_modules\async\dist\async.js:1064:13)
at C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\node_modules\async\dist\async.js:969:16
at ReadFileContext.Fs.readFile (C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\lib\template-assembler.js:114:24)
at ReadFileContext.callback (C:\Users\bigcommerce\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\node_modules\graceful-fs\graceful-fs.js:90:16)
at FSReqWrap.readFileAfterOpen [as oncomplete] (fs.js:420:13)

I was editing a different theme than what stencil was running against.
Added some logging to stencil-cli where the exception was coming from (must restart stencil between edits of these files)
C:\Users\username\AppData\Roaming\nvm\v8.12.0\node_modules\#bigcommerce\stencil-cli\server\plugins\renderer\renderer.module.js
internals.themeAssembler = {
getTemplates: (path, processor, callback) => {
// begin add
console.log(internals.getThemeTemplatesPath(), path);
// end add
TemplateAssembler.assemble(internals.getThemeTemplatesPath(), path, (err, templates) => {
// begin add
if (!templates) {
console.log('templates is empty')
}
// end add
if (templates[path]) {
// Check if the string includes frontmatter configuration and remove it
var match = templates[path].match(/---\r?\n[\S\s]*\r?\n---\r?\n([\S\s]*)$/);
if (_.isObject(match) && match[1]) {
templates[path] = match[1];
}
}
callback(null, processor(templates));
});
},
getTranslations: (callback) => {
LangAssembler.assemble((err, translations) => {
callback(null, _.mapValues(translations, locales => JSON.parse(locales)));
});
},
};

Related

How to access object on Vue front end from axios proxy

I have a locally hosted mongodb database with mongoose, express, axios, and a Vue front end. Right now I'm trying to access a single object from an exported array, but I'm missing the mark and getting "undefined" as the result.
vue.config.js:
module.exports = {
devServer: {
proxy: 'http://localhost:3000',
}
}
here's the front end Vue script meant to use the objects:
import axios from 'axios';
export default {
name: 'Game',
data () {
return {
pages: [],
currentPage: {},
pageTitle: "",
pageText: "",
options: [],
}
},
created () {
this.getPages();
},
methods: {
async getPages() {
try {
let res = await axios.get('/api/pages');
this.pages = res.data;
console.log(this.pages);
this.currentPage = this.pages[0];
console.log(this.currentPage);
return true;
} catch (error) {
console.log(error);
}
},
my "get" endpoint in pages.js:
router.get('/', async (req, res) => {
try {
let pages = await Page.find();
res.send({pages: pages}); //send result of search for pages as list of pages called "pages"
} catch (error) {
console.log(error);
res.sendStatus(500); //500 = server could not fulfill request
}
});
the route in server.js:
const pages = require('./routes/pages');
app.use('/api/pages', pages);
app.listen(3000, () => console.log('Server listening on port 3000!'));
module.exports = app;
and here's the console output, with the "pages" object from vue's data property and the "currentPage" that's supposed to be at pages[0] (printed to console in earlier example):
I can access the api at 'localhost:3000/api/pages' just fine, but how do I break into that array and access the first page object? I want to get an object from the list axios fetches from mongoose, then hold that object in a variable so I can access it's properties. The whole "pages > [[Target]] > pages > [ ]" is part of the problem I'm sure, but I don't know what to tell the code to open it.
Whoops! I realized my mistake. In pages.js I should have sent "res.send(pages);" After a whole couple days too XD

Cannot read properties of undefined (reading 'element') when accessing event object

So I am using Vue Draggable and I am to access the event object in VueJs like below:
<draggable group="leads"
:list="newLeads"
#change="seeChange($event, 'New')">
In methods:
async seeChange(event, status) {
console.log(event.added.element.id);
await axios.patch('http://localhost/api/leads/'+event.added.element.id+'/update-status',
{status : status}).then(response => {
this.leads.unshift(response.data.data);
this.getLeads();
}).catch(error => {
console.log(error);
});
},
I can see the id in the console. But when I drag the card (triggering the event) I get a
Cannot read properties of undefined (reading 'element')
error when the card lands. Even the Axios call gets the correct id and the request is sent fine. But the white screen with this error appears nonetheless.
What am I missing?
Change event is called with one argument containing one of the following properties: added, removed, moved. Docs.
event.added contains information of an element added to the array but when you do removed, moved you haven't property added in object event.
You can use it like this:
async seeChange(event, status) {
console.log(event?.added.element.id);
if(event.added) {
await axios.patch('http://localhost/api/leads/'+event.added.element.id+'/update-status', {status : status}).then(response => {
this.leads.unshift(response.data.data);
this.getLeads();
}).catch(error => {
console.log(error);
});
}
}
request will be send only after added event.
If you want to send request on each event you can do it
by using Object.values(event)[0], example:
async seeChange(event, status) {
const id = Object.values(event)[0].element.id;
console.log(id);
await axios.patch('http://localhost/api/leads/'+id+'/update-status', {status : status}).then(response => {
this.leads.unshift(response.data.data);
this.getLeads();
}).catch(error => {
console.log(error);
});
}
in vue 3 you should use draggable library as below:
install:
npm i -S vuedraggable#next
import:
import Draggable from 'vuedraggable';
and this is the link with full examples:
https://github.com/SortableJS/vue.draggable.next

VueJS getting "undefined" data from ipcRenderer (ElectronJS)

When trying to get a message from ipcMain to ipcRenderer (without node integration and with contextIsolation), it's received but as undefined. Not only that, but if I were to reload the VueComponent (regardless of what change I make to it), the number of responses gets doubled.
For example, the first time I start my application, I get 1x undefined at a time every time I click the button. If I reload the component, I start getting 2x undefined every time I click the button. I reload again and get 4x undefined every time I click the button... and it keeps doubling. If I restart the application, it goes back to 1x.
SETUP
ElectronJS + VueJS + VuetifyJS has been set up as described here.
preload.js as per the official documentation.
import { contextBridge, ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['fromMain']
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
background.js (main process) as per the official documentation for the preload.js file. The omitted code via ... is the default project code generated upon creation.
...
const path = require('path')
const { ipcMain } = require('electron')
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
},
icon: 'src/assets/icon.png',
})
ipcMain.on('toMain', (event, data) => {
console.log(data)
event.sender.send('fromMain', 'Hello IPC Renderer')
// The two lines below return 'undefined' as well in the 'ipcRenderer'
//win.webContents.send('fromMain', "Hello IPC Renderer")
//event.reply('fromMain', 'Hello IPC Renderer')
})
...
}
...
vue.config.js file:
module.exports = {
...
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
}
}
}
main.js (renderer process) contains only the default project code generated upon creation.
VueComponent.vue
<template>
<div id="vue-component">
<v-btn #click="sendMessageToIPCMain()">
</div>
</template>
<script>
export default {
name: "VueComponent",
components: {
//
},
data: () => ({
myData: null,
}),
methods: {
// This works. I get 'Hello IPC Main' in the CMD console.
sendMessageToIPCMain() {
var message = "Hello IPC Main"
window.ipcRenderer.send("toMain", message);
}
},
mounted() {
window.ipcRenderer.receive('fromMain', (event, data) => {
// this.myData = data // 'myData' is not defined error
this.$refs.myData = data;
console.log('myData variable: ' + this.$refs.myData) // undefined
console.log(data) // undefined
})
},
}
</script>
The VueComponent.vue's mounted() has been set up as described here, though If I try to send the data to a variable using this.myData = data, I get an error saying that myData has not been defined - using this.$refs.myData works, though it's still undefined.
P.S. myData has not been defined error =/= undefined. The former is a proper error in red letters while the latter is as seen in the image above.
For solving the first problem (doubling of function calls) you have to remove window.ipcRenderer = ipcRenderer. In contextIsolation mode the approach is to use contextBridge.exposeInMainWorld() only. Using both implementation definitely causes issues.
For the second problem, the callback to receive in ipcRenderer is called with only ...args from main (no event passed to func). see:
ipcRenderer.on(channel, (event, ...args) => func(...args)) <-- func() is called with only args
The only thing you should change is your function in mounted, to accept only data:
window.ipcRenderer.receive('fromMain', (data) => {
console.log(data) // should log you data
})

Error occurs infinitely on nuxt axios. What did I do wrong?

I am currently working on a project with nuxt, and I communicate data with axios.
However, if an error occurs in Axios, it requests Axios indefinitely and stops.
If I make a wrong Axios request, I want an error return right away. What did I do wrong?
The error 'cannot read property 'length' of undefined' occurs at first. And there's an infinite number of errors 'cannot read property 'data' of undefined'
This is code written in plugin.
import SecureLS from 'secure-ls'
export default function({ app, $axios, store }) {
if (process.client) {
const ls = new SecureLS({
encodingType: 'aes',
encryptionSecret: 'key'
})
$axios.defaults.timeout = 3000
$axios.onRequest((config) => {
if (ls.get('key')) {
config.headers.common['X-Token'] = `token ${ls.get('key')}`
}
})
$axios.onError((error) => {
if (
error.response.data &&
error.response.data.detail === 'email confirmed required.'
) {
app.router.push({ name: 'check' })
}
})
}
}

Nuxt: Inside a plugin, how to add dynamic script tag to head?

I'm trying to build a Google Analytics plugin to Nuxt that will fetch tracking IDs from the CMS. I am really close I think.
I have a plugin file loading on client side only. The plugin is loaded from nuxt.config.js via the plugins:[{ src: '~/plugins/google-gtag.js', mode: 'client' }] array.
From there the main problem is that the gtag script needs the UA code in it's URL, so I can't just add that into the regular script object in nuxt.config.js. I need to get those UA codes from the store (which is hydrated form nuxtServerInit.
So I'm using head.script.push in the plugin to add the gtag script with the UA code in the URL. But that doesn't result in the script being added on first page load, but it does for all subsequent page transitions. So clearly I'm running head.script.push too late in the render of the page.
But I don't know how else to fetch tracking IDs, then add script's to the head.
// plugins/google.gtag.client.js with "mode": "client
export default ({ store, app: { head, router, context } }, inject) => {
// Remove any empty tracking codes
const codes = store.state.siteMeta.gaTrackingCodes.filter(Boolean)
// Add script tag to head
head.script.push({
src: `https://www.googletagmanager.com/gtag/js?id=${codes[0]}`,
async: true
})
console.log('added script')
// Include Google gtag code and inject it (so this.$gtag works in pages/components)
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
inject('gtag', gtag)
gtag('js', new Date())
// Add tracking codes from Vuex store
codes.forEach(code => {
gtag('config', code, {
send_page_view: false // necessary to avoid duplicated page track on first page load
})
console.log('installed code', code)
// After each router transition, log page event to Google for each code
router.afterEach(to => {
gtag('event', 'page_view', { page_path: to.fullPath })
console.log('afterEach', code)
})
})
}
I ended up getting this to work and we use it in production here.
Code as of this writing looks like this:
export default ({ store, app: { router, context } }, inject) => {
// Remove any empty tracking codes
let codes = _get(store, "state.siteMeta.gaTrackingCodes", [])
codes = codes.filter(Boolean)
// Abort if no codes
if (!codes.length) {
if (context.isDev) console.log("No Google Anlaytics tracking codes set")
inject("gtag", () => {})
return
}
// Abort if in Dev mode, but inject dummy functions so $gtag events don't throw errors
if (context.isDev) {
console.log("No Google Anlaytics tracking becuase your are in Dev mode")
inject("gtag", () => {})
return
}
// Abort if we already added script to head
let gtagScript = document.getElementById("gtag")
if (gtagScript) {
return
}
// Add script tag to head
let script = document.createElement("script")
script.async = true
script.id = "gtag"
script.src = "//www.googletagmanager.com/gtag/js"
document.head.appendChild(script)
// Include Google gtag code and inject it (so this.$gtag works in pages/components)
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
inject("gtag", gtag)
gtag("js", new Date())
// Add tracking codes from Vuex store
codes.forEach(code => {
gtag("config", code, {
send_page_view: false // Necessary to avoid duplicated page track on first page load
})
// After each router transition, log page event to Google for each code
router.afterEach(to => {
gtag("event", code, { page_path: to.fullPath })
})
})
}
If not in a plug-in, this was a good read on how to load 3rd party scripts: How to Load Third-Party Scripts in Nuxt.js