How to pass multiple plugins to Full Calendar Component in react - reactjs

I am calling the component FullCalendar in my code. For the Calendar I need two plugins to be loaded. The dayGridPlugin and the interactionPlugin. However when I pass them both in the attribute 'plugins', it only loads the first plugin I pass in. I could not find documentation on this specific problem which is why I'm asking here. I'm guessing I made a error in the way I pass them in.
<FullCalendar
defaultView="dayGridMonth"
plugins={[dayGridPlugin, interactionPlugin]}
events={[
{ title: 'event 1', date: '2019-10-01' },
{ title: 'event 2', date: '2019-11-01' }
]}
selectable='true'
/>

The interaction plugin was working but I did not realize that I had to use the dateClick attribute and not the selectable attribute. The dateClick allows you to have a event handler for when for when a date is clicked. It is passed in as the parameter event.

Related

nextJs ServerSideProps and rendering the HTML for SEO

Here is the issue I am having. I am trying to have the Ag-grid render it's html output using NextJS getServerSideProps. However, when I view the source code, it doesn't appear to have any of the HTML rendered for SEO purposes. If I go ahead and output the "staff" array to a div then the HTML output is viewable in the source code so at least I know the function is working. Is there something I need to do to have AGGridReact render its contents?
export default function Home({ staff }) {
const gridRef = useRef();
const defaultColDef = {
resizable: true,
sortable: true,
};
const [columnDefs] = useState([
{ headerName: 'First Name', field: 'first_name' },
{ headerName: 'Last Name', field: 'last_name' },
{ headerName: 'Job Title', field: 'job_title' },
{ field: 'office' },
{ field: 'email' },
{ field: 'phone' },
]);
return (
<>
<main>
<div style={{ height: '600px' }}>
<AgGridReact
id='staff_grid'
ref={gridRef}
rowData={staff}
defaultColDef={defaultColDef}
columnDefs={columnDefs}
rowSelection={'single'}
style={{ height: '100%', width: '100%' }}
></AgGridReact>
</div>
</main>
</>
);
}
// This gets called on every request
export async function getServerSideProps() {
const staff = [];
for (let id = 1; id <= 3; id++) {
staff.push({
id: id,
first_name: 'first' + id,
last_name: 'last' + id,
email: 'member' + id + '#company.com',
phone: '12345' + id,
office: 'place' + id,
job_title: 'Worker ' + id,
});
}
// Pass data to the page via props
return { props: { staff } };
}
TLDR: Diving deep into the ag-grid's node_modules abyss and into their documentation, I found that their grid component is being injected into the DOM (client-side) once an "AG Grid" wrapper component has been mounted. Therefore, this is a client-side only component.
Debugging
When I request the Next page from Postman, I see an empty div where the grid should be:
But when I request the page from the browser, I see the grid:
An even easier way to determine that this is a client-side only component would be to assign the grid a debug prop:
<AgGridReact
debug
rowData={staff}
columnDefs={columnDefs}
rowSelection="single"
/>
We see AG Grid debug logs in the browser (Notice the Rendered on Client message):
But, we don't see any AG Grid debug logs on the server (Notice the Rendered on Server message):
More investigation
I thought I found a server-side rendering solution via their Row Models, but unfortunately it's not referring to the table being SSR'd, but the data being lazy loaded via dynamically fetching data from a server. My guess as to why this table is client-side only is that AG Grid doesn't use a native table, but instead a bunch of div elements with custom styles to represent a table. Since the server doesn't have a DOM (eg, can't access document nor window), calculating these dynamic styles wouldn't be possible.
Alternatives
If you're creating this table for an enterprise and it's absolutely vital to have this page SSR'd for SEO, then I'd recommend having some sort of bot detection in Next's middleware and within gSSP. Then pass an isBot prop to the component and conditionally render a native table (styling won't matter since it's mainly used for SEO). We do something similar for our web application where search results need to be baked into the page on the server, but can be lazy-loaded client-side for a snappier UX.
Here's a working demo. You can change the User-Agent using your browser's tools or by changing it within the request headers.
A more comprehensive bot list can be found here.
What a user sees:
What a bot sees:

Add an event outside of calendar with fullcalendar

I am using fullcalender library to make a big calendar for my project, in their documentation as far as I see, I can only add events by clicking on the empty dates in calendar. But I am trying to add a button outside of a calendar which will show a popup when clicked and user can add an calendar event. I can just create a button outside of calender and call a function like
function handleAddEvent () {
// how do I get selectionInfo here ?
}
<>
<button onClick={handleAddEvent}>Add an event to calender</button>
<FullCalendar
plugins={[
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
listPlugin,
]}
...
/>
</>
but I can't find a way to get the selectionInfo ( is a plain object with the following properties) as it comes from <FullCalendar> and it is necessary to add an event to a calendar. Are there any workarounds for this ? Thanks in advance.
You can see my code sample here
UPDATE
I used customButtons to create an external button to get necessary information from user and add an event along with useRef as calendarRef.current.getApi() to use addEvent to create an event. My codes will be
<FullCalendar
plugins={[
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
listPlugin,
]}
ref={calendarRef}
headerToolbar={{
left: "prev,next today customAddEventBtn",
center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
}}
initialView="dayGridMonth"
customButtons={{
customAddEventBtn: {
text: "Add an event",
click: handleCustomAddEventBtn,
},
}}
/>
function handleCustomAddEventBtn() {
let calendarApi = calendarRef.current.getApi();
if (title) {
calendarApi.unselect(); // clear date selection
calendarApi.addEvent({
id: uuidv4(),
title,
start: selectedFromDate, // state to get date form user
end: selectedToDate, // state to get date form user
allDay: true,
});
}
}

Tabulator - custom cell editor not fully opened

The requirement is to have 'inline editing' of some cell in Tabulator based table.
The cell requires a custom editor since the input is a custom component (which is already used in another form, outside of Tabulator).
Our environment is React + Tabulator (v4.7) + BlueprintJS as the components library.
The problem is that the component won't fully open as a custom editor in Tabulator, while working fine outside of Tabulator, in a regular form.
Why the component won't fully open?
The custom component serving as the 'editor' for this cell is using Blueprint (BP) 'popover' so it has a popover target and a content. This is how it looks like in a form edit:
The problem is that upon clicking, the popover target is being rendered but the popover content is not, so the custom 'dropdown' input component is never appearing:
Relevant code sections
Tabulator column definition:
{
title: "Some Col Title",
field: "someField",
formatter: someFieldFormatter,
editor: "someFieldEditor" as Tabulator.Editor,
editorParams: (cell) => {
return { cell, zones: zonesDataTree };
},
},
Defining custom cell editor:
Tabulator.prototype.extendModule("edit", "editors", {
someFieldEditor: (
cell: CellComponent,
onRendered: Function,
success: Function,
cancel: Function,
editorParams: SomeFieldCellEditorParams
): Element => {
const elem = document.createElement("div");
// SomeFieldCellEditor is a React component that wraps around the same component used in
// the "regular form" scenario mentioned in the screenshot
const someFieldCellEditorComponent: any = React.createElement(SomeFieldCellEditor, {
theData: editorParams.data,
});
ReactDOM.render(someFieldCellEditorComponent, elem);
return elem;
},
});
This is happening because the element is contained inside of the cell.
To prevent corruption of the table layout, tabulator cells have overflow:hidden defined on their CSS.
Most dropdown libraries have a an option that lets you set where in the DOM the list should be appended. You can use this potion to append the list to the body tag which should resolve the issue.
On a side note, did you know that Tabulator comes with built in Select and AutoComplete Editors

Is there a way to add new tab in WordPress Gutenberg editor

I am completely new to Gutenberg and I need to add a new tab in the setting section Please check this screenshot
I Have created some blocks for Gutenberg but no experience in this. I tried this code
import { TabPanel } from '#wordpress/components';
const onSelect = ( tabName ) => {
console.log( 'Selecting tab', tabName );
};
const MyTabPanel = () => (
<TabPanel className="my-tab-panel"
activeClass="active-tab"
onSelect={ onSelect }
tabs={ [
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
},
] }>
{
( tab ) => <p>{ tab.title }</p>
}
</TabPanel>
);
But didn't help me. Anyone here please help me.
Thanks in advance
In the screenshot you provided, the location you are attempting to add a tab to is the Settings Header
(gutenberg/packages/edit-post/src/components/sidebar/settings-header) for which there is not currently a slot in the Gutenberg API to extend from (although this could potentially be done, it's best to not interfere with core UI).
The prefered method to add to the Admin UI is to use an provided SlotFill for custom content, currently there are:
PluginBlockSettingsMenuItem
PluginDocumentSettingPanel
PluginMoreMenuItem
PluginPostPublishPanel
PluginPostStatusInfo
PluginPrePublishPanel
PluginSidebar
PluginSidebarMoreMenuItem
The PluginSidebar slot is useful for adding custom content that is specific for your plugins/blocks purpose. The main point to consider is whether the content you wish to add applies just to your block, the post/page as a whole or is some other 'global' setting to do with a plugin.
If your content applies to the whole post/page, the PluginPostStatusInfo slot may be a good location to add to. You could also add your own Panel that appears underneath the "Document" tab.
If the content is block-specific, then you can use the to add custom controls underneath "Block" tab that contextually show when your block is selected. This would also be a good place for meta field values that are specific to the block or custom controls for colors/display options related to your block.
The official WordPress Gutenberg documentation also has a tutorial on Block Controls: Block Toolbar and Settings Sidebar which walks through some common scenarios for adding your own settings in Blocks.

Angularjs fullcalendar drag drop - get value of dropped object

I am dropping an external object into angular-ui-calendar using angular-dragdrop.
The external object is coming from this list:
<div class="fc-event" data-drag="true"
jqyoui-draggable="{animate:true}"
ng-model="test_object"
ng-repeat="test_object in test_objects">
Draggable - {{ test_object.name }}
</div>
The fullcalendar is set up with:
<div id="ApptsCalendar" calendar="ApptsCalendar"
ui-calendar="calendarOptions.calendar"
ng-model="eventSources" data-drop="true"
jqyoui-droppable="{multiple:true, onDrop: 'drop_function'}"
data-jqyoui-options>
</div>
When dropped, I can process that event using fullcalendar 'drop' method with:
$scope.calendarOptions = {
calendar: {
editable: true,
droppable: true,
drop: function(date,jsEvent,ui,resourceId){
console.log("Dropped from calendarOptions")
console.log(resourceId);
$scope.eventSources[0].push({
id:5,
title: 'dropped event (fake)',
start: date
});
}
}
};
or from the angular-dragdrop 'onDrop' callback to call a 'drop' function:
jqyoui-droppable="{multiple:true, onDrop: 'drop'}"
Both can trigger when I want, but neither seem to have the two pieces I need. I need to have the object value being dropped (defined in ng-model) and the date being dropped into.
Basically, I want to push the event to the the eventSources with:
$scope.eventSources[0].push({
id:5,
title: '...name of object...',
start: '...date of target dropped on...'
});
http://plnkr.co/edit/fj858Htb2FRUg5h1pucP?p=preview
Well, one of the things you wanted is already there. It's date on which the event is dropped. You get it from the first argument of the drop function. It's a moment object (according to the docs) so you might want to use .toDate() in order to get the JS Date object.
The other thing is the value of the event which got dropped. According to the same docs page, the DOM object of the event is accessible using this inside drop function.
Now, this is a bit unconventional way (I don't see many choices here), what you can do is, with the ng-repeat iterating over event objects, you can keep an attribute with value from each object which can later be accessed inside the drop function. For example, see how I added customEventName="{{test_object.name}}" in here:
<div class="fc-event tech_draggable" data-drag="true" id="{{test_object.name}}"
customEventName="{{test_object.name}}" jqyoui-draggable="{animate:true}"
ng-model="test_object" ng-repeat="test_object in test_objects" ...>
Draggable - {{ test_object.name }}
</div>
Then, in the drop function, that can be accessed using this.getAttribute('customEventName') like this:
$scope.calendarOptions = {
calendar: {
editable: true,
droppable: true,
drop: function(momentDate, jsEvent, ui, resourceId) {
console.log(momentDate.toDate()) // gives JS Date object
console.log(this.getAttribute('customEventName')); // gives event2/event3 etc.
//... more
}
}
};
An alternative is to make an attribute with a string representing the scope variable name:
<div ng-repeat="test_object in test_objects">
<div class="fc-event tech_draggable"
data-drag="true"
jqyoui-draggable="{animate:true}"
ng-repeat="test_object in test_objects"
style="margin-bottom:1px;"
data-jqyoui-options="{helper: 'clone'}"
scope-data-name="test_objects[{{$index}}]"
>
Draggable - {{ test_object.name }}
</div>
</div>
And using $scope.$eval to get the actual object:
$scope.calendarOptions = {
calendar: {
editable: true,
droppable: true,
drop: function(date,jsEvent,ui,resourceId){
var scopeDataName = this.getAttribute('scope-data-name');
var data = $scope.$eval(scopeDataName);
$scope.eventSources[1].push({
id: $scope.eventSources[0].length,
title: `${data.name} ${data.description}`,
start: date
});
}
}
};
The DEMO on PLNKR
After some more research, I think fullcalendar has the solution already.
I can use data-event attribute in the element:
data-event='{"title":"{{ test_object.name }}"}'
With that, there is no need to even have a 'drop' function... fullcalendar natively supports drag and drop.
I can then optionally use eventReceive to handle a drop from an external resource and use eventDrop to handle an internal event move.
http://plnkr.co/edit/fj858Htb2FRUg5h1pucP?p=preview

Resources