Can I access attributes with source meta from the Save class? - javascript

I've been building a plugin for a client that allows the user to select some colours and a set of fonts per post that is then applied throughout the post in every block.
The information is stored in the post's metadata
This is done through a sidepanel inside the post and since all blocks on the page need to access the data, the information is stored in the post's metadata.
Storing and retrieving the information is fine from the side panel and the retrieving information from the Edit class of each block is fine too (The blocks don't need to change the data).
Unfortunately, I'm having trouble with the Save class.
I know that I can't use the data module and withSelect from within the Save class, but the information in the documentation led me to believe that if I define the information as an attribute with source "meta" I could use it in the same way as a normal attribute.
"From here, meta attributes can be read and written by a block using the same interface as any attribute..."
- Documentation
This is what I've done with the Edit class successfully, but it doesn't work with the Save class. Instead, the attributes simply aren't available (ie. when I console log the attributes it doesn't include those ones).
My thought at the moment
I'm thinking the documentation must mean "only within the edit class", but I'd like to know if anyone else knows for sure...
The code
I don't think the code is very relevant because it works everywhere except the Save class, but here are my attribute definitions just fyi:
attributes: {
buttons: {
type: "array",
source: "query",
selector: "a",
query: {
text: {
type: "string",
source: "html",
attribute: "a"
},
url: {
type: "string",
source: "attribute",
attribute: "href"
},
colorKey: {
type: "string",
source: "attribute",
attribute: "data-color-key"
},
},
},
fontSetKey: {
type: "string",
source: "meta",
meta: "eimMeta_fontSetKey",
},
primaryColor: {
type: "string",
source: "meta",
meta: "eimMeta_primaryColor",
},
primaryContrastingShade: {
type: "string",
source: "meta",
meta: "eimMeta_primaryContrastingShade",
},
secondaryColor: {
type: "string",
source: "meta",
meta: "eimMeta_secondaryColor",
},
secondaryContrastingShade: {
type: "string",
source: "meta",
meta: "eimMeta_secondaryContrastingShade",
},
},
And here is my save function
import { Component } from "#wordpress/element";
import { RichText } from "#wordpress/editor";
import plugin from "../../plugin";
import category from "../category";
import block from "./block";
import { preparePostMetaForUse, getColorHexWithKey } from "../../common/values";
class Save extends Component {
render() {
const { attributes } = this.props;
const { className, buttons } = attributes;
console.log("Save attributes:");
console.log(attributes);
const postMeta = preparePostMetaForUse( attributes );
let jsxButtons = buttons.map((button) => ( <>
<RichText.Content
tagName="a"
value={button.text}
href={button.url}
data-color-key = {button.colorKey}
role="button"
target="_blank"
rel="noopener noreferrer"
style={{
fontFamily: postMeta.fontSet.body.fontFamily,
fontWeight: postMeta.fontSet.body.fontWeight,
fontSize: postMeta.fontSet.body.fontSize,
letterSpacing: postMeta.fontSet.body.letterSpacing,
lineHeight: postMeta.fontSet.body.lineHeight,
color: getColorHexWithKey(button.colorKey, postMeta.colorSet).contrastingShade,
backgroundColor: getColorHexWithKey(button.colorKey, postMeta.colorSet).color,
}}
/>
</> ));
return <div className={className}>{jsxButtons}</div>;
}
}
export default Save;

Related

For each object Javascript

hope all is fine for you
I'm a beginner in javascript and i'm trying to integrate a video customer review API on my website.
The integration is working on all my pages but on one product page i'd like to init the sdk for each videos id returned by the object to display all the video reviews of this product.
The sdk returned an object as below : enter image description here
And i have to display the sdk like that :
SDK.init(
{
type: "player",
args: {
key: playerKey,
id: videoId, // Id returned by the object on the screenshot
width: '100%',
height: "inherit",
},
},
videoPlayer //=> my div element
)
i tried the for each method, objects as below but nothing work for me, each time only 1 player is displaying.
Object.keys(videos).forEach(id => {
console.log(videos);
SDK.init(
{
type: "player",
args: {
key: playerKey,
id: videoId,
width: '100%',
height: "inherit",
},
},
videoPlayer
)
});
I have no archive what i tried before this but i'm a little bit lost
Have a nice day and thank you
The documentation indicates that one HTML element should be dedicated to one player. So you cannot put several players in one element.
You could create child elements inside your container element.
Assuming that videos is an array, like depicted in the screenshot:
videos = [
{ id: '....' },
{ id: '....' }
]
then do this:
for (let {id} of videos) {
console.log("video ID", id);
const wrapper = document.createElement("div");
videoPlayer.appendChild(wrapper);
SDK.init(
{
type: "player",
args: {
key: playerKey,
id,
/* any other properties you want to add here */
},
},
wrapper
);
}
I didn't try the SDK, but as #T.J. Crowder mentioned in the comment you probably need to create new <div> for every init call, something like this:
Object.keys(videos).forEach(id => {
console.log(videos);
const node = document.createElement("div");
node.style.width = '500px';
node.style.height = '500px';
videoPlayer.appendChild(node)
SDK.init({
type: "player",
args: {
key: playerKey,
id: videoId,
width: '100%',
height: "inherit",
},
}, node)
});

How can I conditionally disable a control in Storybook based on the value of another argument?

I am trying to conditionally disable a Storybook.js control based on the value of another argument. For example, I have a modal component that can be of type 'alert', 'confirmation', 'content', or 'photo'. All of these modal types, except for 'photo', also require a content prop of type string. The photo modal does not require this content prop because it does not display any text.
So I would like to disable the content control in Storybook whenever the type prop is selected as 'photo'.
I first tried writing a custom propType validator, but Storybook thinks this prop is supposed to be a function:
Custom PropType validator in Storybook
Now I am trying to disable the control in the component's storybook file:
export default {
title: 'Global Design System/Modal',
component: Modal,
argTypes: {
type: {
control: {
type: 'select',
options: [
'alert',
'confirmation',
'content',
'photo'
]
}
},
content: {
table: {
disable: function() {
return this.argTypes.type === 'photo'
}
}
}
},
};
But in this case I do not have a way of referencing the current value of 'type'
It can not for now!. I saw member SB create PR for similar feature but it closed without merging.
There are now conditional control options: https://storybook.js.org/docs/react/essentials/controls#conditional-controls
You can do something like the following:
export default {
title: 'Global Design System/Modal',
component: Modal,
argTypes: {
type: {
control: {
type: 'select',
options: [
'alert',
'confirmation',
'content',
'photo'
]
}
},
content: {
...,
if: { arg: 'type', neq: 'photo' },
}
},
};

How to add an action opening a dropdown menu in Monaco Editor?

I know how to add an entry in the context menu of the Monaco editor, with editor.addAction. How to add an action which opens a dropdown list, as the "Command Palette" action?
As mentioned in this issue, the quick-open api is not yet implemented in monaco-editor. But after a series of in-depth research, I found a somewhat hacker solution.
First, you need to import IQuickInputService like below.
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';
import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput';
Then, create our editor.
// Create your editor instance...
let editor = monaco.editor.create(
// ...
);
// Add a new command, for getting an accessor.
let quickInputCommand = editor.addCommand(0, (accessor, func) => {
// a hacker way to get the input service
let quickInputService = accessor.get(IQuickInputService)
func(quickInputService)
});
Finally, as an example, we can add an action.
editor.addAction({
id: "myQuickInput",
label: "Test Quick Input",
run: (editor) => {
// create quick input
editor.trigger("", quickInputCommand, (quickInput) => {
quickInput.pick([
{
type: 'item',
id: "my_quick_item",
label: "MyQuickItem",
},
{
type: 'item',
id: "my_quick_item2",
label: "MyQuickItem2",
},
{
type: 'item',
id: "my_quick_item3",
label: "MyQuickItem3",
},
]).then((selected) => {
console.log(selected)
})
})
}
})
For the interface of IQuickInputService, please refer to here here
If you do it right, when you run the action, you should get the same result as I did.

Programmatically create Gatsby pages from Contentful data

I am looking for help with GatsbyJS and Contentful. The docs aren't quite giving me enough info.
I am looking to programmatically create pages based on contentful data. In this case, the data type is a retail "Store" with a gatsby page at /retail_store_name
The index.js for each store is basically a couple of react components with props passed in e.g. shop name and google place ID.
Add data to contentful. Here is my example data model:
{
"name": "Store"
"displayField": "shopName",
"fields": [
{
"id": "shopName",
"name": "Shop Name",
"type": "Symbol",
"localized": false,
"required": true,
"validations": [
{
"unique": true
}
],
"disabled": false,
"omitted": false
},
{
"id": "placeId",
"name": "Place ID",
"type": "Symbol",
"localized": false,
"required": true,
"validations": [
{
"unique": true
}
],
"disabled": false,
"omitted": false
}
}
I've added the contentful site data to gatsby-config.js
// In gatsby-config.js
plugins: [
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: `your_space_id`,
accessToken: `your_access_token`
},
},
];
Query contentful - I'm not sure where this should happen. I've got a template file that would be the model for each store webpage created from contentful data.
As mentioned this is just some components with props passed in. Example:
import React, { Component } from "react";
export default class IndexPage extends Component {
constructor(props) {
super(props);
this.state = {
placeId: "",
shopName: "",
};
}
render (){
return (
<ComponentExampleOne shopName={this.state.shopName} />
<ComponentExampleTwo placeId={this.state.placeId} />
);
}
I'm really not sure how to go about this. The end goal is auto publishing for non-tech users, who post new stores in Contentful to be updated on the production site.
You can create pages dynamically at build time and to do that you need to add some logic to the gatsby-node.js file. Here is a simple snippet.
const path = require('path')
exports.createPages = ({graphql, boundActionCreators}) => {
const {createPage} = boundActionCreators
return new Promise((resolve, reject) => {
const storeTemplate = path.resolve('src/templates/store.js')
resolve(
graphql(`
{
allContentfulStore (limit:100) {
edges {
node {
id
name
slug
}
}
}
}
`).then((result) => {
if (result.errors) {
reject(result.errors)
}
result.data.allContentfulStore.edges.forEach((edge) => {
createPage ({
path: edge.node.slug,
component: storeTemplate,
context: {
slug: edge.node.slug
}
})
})
return
})
)
})
}
the createPages that was exported is a Gatsby Node API function you can find the complete list in the docs here.
For the query allContentfulStore it's called like that because your contentType name is store the gatsby query will be allContentful{ContentTypeName}.
Finally, I created a youtube video series explaining how you can build a Gatsby website with Contentful. You can find it here
I hope this answer your question.
Cheers,
Khaled

Vue.js - recursive call of the component through render function. (runtime only)

I have one big request for you. I am a high-school student and I want to create an app for students with my friend. In the begining we wanted to use React for our reactive components, but then we saw Vue and it looked really good. But because of the fact, that we already have a big part of the app written in twig, we didn't want to use Vue.js standalone, because we would have to change a lot of our code, especially my friend, which is writing backend in Sympfony. So we use the runtime only version, which does not have a template option, so i have to write render functions for our components. And i am stucked with one particular problem.
I am writing a file-manager, and i need to render layer for every folder. Code is better then million words, so, take a look please :
var data = {
name: 'My Tree',
children: [
{
name: 'hello',
isFolder: false,
},
{
name: 'works',
isFolder: true,
children: [
{
name: 'child2',
isFolder: true,
},
{
name: 'child3',
isFolder: false,
},
]
}
]
}
Vue.component('layer', {
render: function renderChild (createElement) {
if(data.children.length){
return createElement('ul', data.children.map(function(child){
return createElement('li', {
'class' : {
isFolder: child.isFolder,
isFile: !child.isFolder
},
attrs: {
id: "baa"
},
domProps: {
innerHTML: child.name,
},
on:{
click: function(){
console.log("yes");
},
dblclick: function(){
console.log("doubleclicked");
if(child.children.length){
// if this has children array, create whole "layer" component again.
}
}
}}
)
}))
}
},
props: {
level: {
type: Number,
required: true
},
name: {
type: String,
}
}
})
new Vue({
el: '#fileManagerContainer',
data: data,
render (h) {
return (
<layer level={1} name={"pseudo"}>
</layer>
)
}
})
My question is, how to write that recursive call, which will render the whole Layer component on the doubleclick event, if the element has children array.
Thank you in advance for any reactions, suggestions or answers :)
I know this is a very old question, so my answer won't be useful to the OP, but I wanted to drop in to answer because I found myself with the very same problem yesterday.
The answer to writing these recursive render functions is to not try to recurse the render function itself at all.
For my example, I had a set of structured text (ish) - An array of objects which represent content - which can be nested, like so:
[
// each array item (object) maps to an html tag
{
tag: 'h3',
classes: 'w-full md:w-4/5 lg:w-full xl:w-3/4 mx-auto font-black text-2xl lg:text-3xl',
content: 'This is a paragraph of text'
},
{
tag: 'img',
classes: 'w-2/3 md:w-1/2 xl:w-2/5 block mx-auto mt-8',
attrs: {
src: `${process.env.GLOBAL_CSS_URI}imgsrc.svg`,
alt: 'image'
}
},
{
tag: 'p',
classes: 'mt-8 text-xl w-4/5 mx-auto',
content: [
{
tag: 'strong',
content: 'This is a nested <strong> tag'
},
{
content: ' '
},
{
tag: 'a',
classes: 'underline ml-2',
content: 'This is a link within a <p> tag',
attrs: {
href: '#'
}
},
{
content: '.'
}
]
}
]
Note the nested elements - These would need recursion to render properly.
The solution was to move the actual rendering work out to a method as follows:
export default {
name: 'block',
props: {
block: {
type: Object,
required: true
}
},
methods: {
renderBlock (h, block) {
// handle plain text without a tag
if (!block.tag) return this._v(block.content || '')
if (Array.isArray(block.content)) {
return h(block.tag, { class: block.classes }, block.content.map(childBlock => this.renderBlock(h, childBlock)))
}
// return an html tag with classes attached and content inside
return h(block.tag, { class: block.classes, attrs: block.attrs, on: block.on }, block.content)
}
},
render: function(h) {
return this.renderBlock(h, this.block)
}
}
So the render function calls the renderBlock method, and that renderBlock method is what calls itself over and over if it needs to - The recursion happens within the method call. You'll see that the method has a check to see whether the content property is of an Array type - At this point it performs the same render task, but rather than passing the content as-is, it passes it as an Array map, calling the same render method for each item in the array.
This means that, no matter how deeply the content is nested, it will keep calling itself until it has reached all the way to the "bottom" of the stack.
I hope this helps save someone some time in the future - I certainly wish I'd had an example like this yesterday - I would have saved myself a solid couple of hours!

Categories

Resources