is my browser compatible for this web app (pwa)? whatwebcando - progressive-web-apps
Ive created a number of web apps (pwa's) each has its own feature requirements. Where i am falling short is with browser compatibility for my apps.
At the moment i am forcing the use of Chrome, Edge, Samsung browser and safari for ios.
If i check at https://caniuse.com it tells me, for example, Safari does not support background sync, and other browser's don't support other features. It does not say that chrome or edge on ios supports background sync or alternative browsers or versions to use.
Also there are many browsers each with their own following and different versions within each browser which do and do not support specific features. . I would like to support as many as possible.
What i would like to do is create/or download a script/plugin that when the user lands on the apps info page, the script will detect if the browser supports all the features required by the specific pwa, like service-worker.js, Geolocation, background sync, augmented reality, etc etc, and if it does, show the install button or recommend an alternative browser/version.
Something like||Exactly like the feature detection of https://whatwebcando.today/.
Is there a plugin or script already that can do this OR do i have to create my own from scratch?
I have tried searching for something, but all I'm getting is online tools and scripts like feature.js, which is limiting/limited and other websites like https://whatwebcando.today/
You could use Modernizr to do the feature detection and let the user know what features are supported, then update the page accordingly.
Modernizr.on('serviceworker', (result) => {
if (result) {
console.log('Service workers are supported.');
} else {
console.log('Service workers are NOT supported.');
}
});
It seems i am allowed to post answers now:
here is my test function for browser compatibility:
var testArr;
function detectFeatures(registration) {
var win = window,
doc = document,
nav = navigator,
scr = screen;
return {
"Installable": "serviceWorker" in nav,
"Home Screen": "relList" in HTMLLinkElement.prototype && doc.createElement("link").relList.supports("manifest") && "onbeforeinstallprompt" in win || "BeforeInstallPromptEvent" in win || "setAppBadge" in nav,
"Installed Apps": "getInstalledRelatedApps" in nav,
"Foreground Detection": "visibilityState" in doc || doc.visibilityState,
"Freeze Detection": "onresume" in doc || "onfreeze" in doc,
"Resume Detection": "onresume" in doc || "onfreeze" in doc,
"Permissions": "permissions" in nav,
"Network type": "connection" in nav || "mozConnection" in nav || "webkitConnection" in nav || "msConnection" in nav,
"Network speed": "connection" in nav || "mozConnection" in nav || "webkitConnection" in nav || "msConnection" in nav,
"Online state": "onLine" in nav,
"Background Sync": "SyncManager" in win || "sync" in registration,
"Periodic Background Sync": "periodicSync" in registration || "SyncManager" in win || "periodicSync" in win || "periodicSync" in nav,
"Background Fetch": "backgroundFetch" in registration,
"Task scheduling": "Notification" in win && "showTrigger" in Notification.prototype || Notification.prototype.showTrigger || "taskScheduler" in registration || "alarms" in nav || "periodicSync" in registration,
"Notifications": "Notification" in win || "showNotification" in registration,
"Push Messages": "PushManager" in win || "pushNotification" in win.safari || win.safari.pushNotification || "pushManager" in registration,
"Offline Capabilities": "caches" in win,
"Cache Support": "caches" in win,
"Offline Storage": "localStorage" in win || "sessionStorage" in win,
"Storage": "storage" in nav && "StorageManager" in win,
"Persistent Storage": "storage" in nav && "persist" in nav.storage || "storage" in nav && "persisted" in nav.storage,
"Storage Quotas": "storage" in nav && "estimate" in nav.storage,
"IndexedDb": "indexedDB" in win || "mozIndexedDB" in win || "webkitIndexedDB" in win || "msIndexedDB" in win,
"File Access": "File" in win || "chooseFileSystemEntries" in win,
"File Api": "File" in win && "FileReader" in win && "FileList" in win && "Blob" in win || "chooseFileSystemEntries" in win,
"Clipboard": "clipboard" in nav || "ClipboardEvent" in nav || "oncut" in doc || "onpast" in doc || "oncopy" in doc,
"Pointing Device Apadtation": win.matchMedia("(pointer: none), (pointer: coarse), (pointer: fine)").matches,
"Navigation Preload": "navigationPreload" in registration,
"Contacts": "contacts" in nav || "mozContacts" in nav,
"SMS": "messaging" in nav,
"Geolocation": "geolocation" in nav,
"Geofencing": "GeofenceManager" in win,
"NDEFReader": "NDEFReader" in win,
"Device Memory": "deviceMemory" in nav,
"Touch Gestures": "ontouchstart" in win || "onpointerdown" in win,
"Idle Detection": "IdleDetector" in win,
"Presentation Features": "presentation" in nav || "PresentationRequest" in win,
"Fullscreen": "requestFullscreen" in doc.documentElement || "mozRequestFullScreen" in doc.documentElement || "webkitRequestFullscreen" in doc.documentElement || "msRequestFullscreen" in doc.documentElement,
"Screen Orientation": "orientation" in scr || "mozOrientation" in scr || "msOrientation" in scr,
"Orientation Lock": "orientation" in scr || "mozOrientation" in scr || "msOrientation" in scr,
"Wake lock": "keepAwake" in scr || "wakeLock" in nav,
"Device Position": "DeviceOrientationEvent" in win || "AbsoluteOrientationSensor" in win || "RelativeOrientationSensor" in win,
"Device Motion": "DeviceMotionEvent" in win || "LinearAccelerationSensor" in win && "Gyroscope" in win,
"Web Share": "share" in nav,
"File Share": "share" in nav && "canShare" in nav,
"Battery Status": "getBattery" in nav || "battery" in nav,
"Vibrate": "vibrate" in nav,
"USB": "usb" in nav || nav.usb,
"Serial Port": "serial" in nav || nav.serial,
"Ambient Light": "ondevicelight" in win || "AmbientLightSensor" in win,
"Bluetooth": "bluetooth" in nav,
"Midi devices": "requestMIDIAccess" in nav,
"Proximity Sensors": "ondeviceproximity" in win || "onuserproximity" in win || "ProximitySensor" in win,
"GravitySensor": "GravitySensor" in win,
"Magnetometer": "Magnetometer" in win,
"Gyroscope": "Gyroscope" in win,
"Accelerometer": "Accelerometer" in win,
"Linear Acceleration Sensor": "LinearAccelerationSensor" in win,
"Media Capabilities": "mediaCapabilities" in nav,
"mediaDevices": "mediaDevices" in nav || "getUserMedia" in nav || "webkitGetUserMedia" in nav || "mozGetUserMedia" in nav || "msGetUserMedia" in nav,
"Camera": "mediaDevices" in nav || "getUserMedia" in nav || "webkitGetUserMedia" in nav || "mozGetUserMedia" in nav || "msGetUserMedia" in nav,
"Media Session": "mediaSession" in nav,
"Image Capture": "ImageCapture" in win || "ImageCapture" in nav,
"Recording Media": "MediaRecorder" in win || "MediaRecorder" in nav,
"Real-Time Communication webrtc": "RTCPeerConnection" in win || "webkitRTCPeerConnection" in win,
"Audio": "mediaDevices" in nav || "getUserMedia" in nav || "webkitGetUserMedia" in nav || "mozGetUserMedia" in nav || "msGetUserMedia" in nav,
"Audio Capture": "mediaDevices" in nav || "getUserMedia" in nav || "webkitGetUserMedia" in nav || "mozGetUserMedia" in nav || "msGetUserMedia" in nav,
"Video": "mediaDevices" in nav || "getUserMedia" in nav || "webkitGetUserMedia" in nav || "mozGetUserMedia" in nav || "msGetUserMedia" in nav,
"Video Capture": "mediaDevices" in nav || "getUserMedia" in nav || "webkitGetUserMedia" in nav || "mozGetUserMedia" in nav || "msGetUserMedia" in nav,
"Augmented Reality": "getVRDisplays" in nav || "xr" in nav,
"Virtual Reality": "getVRDisplays" in nav || "xr" in nav,
"Speech Recognition": "SpeechRecognition" in win || "webkitSpeechRecognition" in win,
"Barcode scanner": "BarcodeDetector" in win,
"Facial recognition": "FaceDetector" in win,
"Text Detection": "TextDetector" in win,
"Payment Request": "PaymentRequest" in win,
"Payment Handler": "paymentManager" in registration,
"Credential Management": "credentials" in nav &&
"preventSilentAccess" in nav.credentials &&
("PasswordCredential" in win || "FederatedCredential" in win)
};
};
function setCompatibilityArray(browserFeatures) {
for (const feature in browserFeatures) {
if (!browserFeatures.hasOwnProperty(feature)) {
continue;
}
var nf = feature.toString().replace(/[^A-Za-z0-9]/g, '').toLowerCase();
var val = browserFeatures[feature] ? "1" : "0";
testArr[nf] = {
'name': '' + feature + '',
'value': val
};
}
_set('browserFeature', testArr, 1);
//here use console.log(testArr); to get the variable names for your requirements array
};
function getFeatures() {
testArr = _get('browserFeature', 0, 1);
if (testArr == 0) {
if ("serviceWorker" in navigator) {
testArr = {};
return navigator.serviceWorker.register("service-worker.js")
.then((registration) => {
const browserFeatures = detectFeatures(registration);
setCompatibilityArray(browserFeatures);
});
}
}
}
how to use it:
on document.load || .ready place this function, getFeatures();
the _get() and _set() functions gets and sets a local storage variable of the array testArr which contains all the features of the browser and whether the feature is true or false.
in the testArr the key name is the feature names that are joined and in lowercase, you can create your own "required features" array using these key names.
like so:
var canwork = true;
var permissionReqArr = ['one','two','three','four','five'];
// permissionReqArr contains the variables requirements for app to work,
you can get this from the getFeatures function by using console.log() on the result testArr.
then:
for(var i = 0; i < permissionReqArr.length; i++){
var check = testArr[permissionReqArr[i]];
if(check == false || check == 0){
canwork = false;
}
}
if(canwork == false){
alert('your browser is not compatible, you will not be able to download/install this app');
}
if this helps you in anyway please vote me up, i need the points..
notes:
if you don't want to actually install the service-worker.js, then use this code inside your service-worker.js file:
self.addEventListener("install",()=>{}), self.addEventListener("activate", ()=>self.clients.claim());
//Will not install anything in the browser only return the registration event to capture other features of the browser
also this Function will only call the service-worker if the features localstorage item is not set.. if you do have a service worker, then make sure to remove this block from the above..
the _set and _get functions return zero, if the localstorage item is not set, you can make your own function for this..
Related
Recently used `MutationObserver` on github.com to monitor node change failure
Last few days using MutationObserver on github.com site to monitor node changes intermittent failure. Demo: any gist url: https://gist.github.com/maboloshi/a4b1f27567319d4a42352aadd036a578 When you click on Revisions, the alert dialog box does not pop up Demo Code: const m = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; new m(function(mutations) { alert('Monitor to page title change'); }).observe( document.querySelector('title'), { childList: true } );
Escaping from a nested material-ui dialog
When I have one dialog open and it opens another and I hit escape it closes both. Is there any way to make the escape only close the top most dialog?
I dont think this will be possible without some hassle. In the dialog render function, this eventlistener is rendered and there is no prop that allows you to overwrite this. {open && <EventListener target="window" onKeyUp={this.handleKeyUp} onResize={this.handleResize} /> } Which calls this method. handleKeyUp = (event) => { if (keycode(event) === 'esc') { this.requestClose(false); } }; source You can however dive into in node_modules/material-ui/Dialog/dialog.js and delete that code or change it. Removing this line will prevent it from ever closing on esc, but will count for all dialogs. Maybe after that you can implement a keycode event listener in you own class that handles the closing of the modal. if ((0, _keycode2.default)(event) === 'esc') { _this2.requestClose(false); } EDIT: possible solution. I created 2 components, a DialogContainer class component and a Dialog functional component. To use this you have to npm install --save react-event-listener. For this to work you still have to remove the above code from the node_modules. When only one dialog is opened it will close that dialog when esc is clicked. If two dialogs are opened it will first close dialog2 and leave dialog1 open. DialogContainer.js import React, { Component } from 'react'; import Dialog from './Dialog'; import RaisedButton from 'material-ui/RaisedButton'; import EventListener from 'react-event-listener'; export default class DialogContainer extends Component { state = { openDialog1: false, openDialog2: false }; handleDialog1Open = () => { this.setState({ openDialog1: true }); }; handleDialog2Open = () => { this.setState({ openDialog2: true }); }; handleDialog1Close = () => { this.setState({ openDialog1: false }); }; handleDialog2Close = () => { this.setState({ openDialog2: false }); }; handleKeyUp = (event) => { // 27 = esc if (event.keyCode === 27) { if (this.state.openDialog1 && this.state.openDialog2) { this.handleDialog2Close(); } else { this.handleDialog1Close(); this.handleDialog2Close(); } } }; render() { return ( <div> {(this.state.openDialog1 || this.state.openDialog2) && <EventListener target="window" onKeyUp={this.handleKeyUp} />} <RaisedButton label="Open1" onTouchTap={this.handleDialog1Open}/> <RaisedButton label="Open2" onTouchTap={this.handleDialog2Open}/> <Dialog openOtherDialog={this.handleDialog2Open} open={this.state.openDialog1} handleClose={this.handleDialog1Close} number={1}/> <Dialog open={this.state.openDialog2} handleClose={this.handleDialog2Close} number={2}/> </div> ) } }; Dialog.js import React from 'react'; import Dialog from 'material-ui/Dialog'; import RaisedButton from 'material-ui/RaisedButton'; const DialogCustom = ({ open, handleClose, number, openOtherDialog}) => { return ( <div> <Dialog title="Dialog" modal={false} open={open} onRequestClose={handleClose} > {`this is dialog ${number}`} {openOtherDialog && <RaisedButton label="Open2" onTouchTap={openOtherDialog}/> } </Dialog> </div> ); }; export default DialogCustom;
There is nearly always a better solution than having two dialogs/modals open at the same time. In our material-design app using MUI, we have this situation on a few occasions. The way we handle it: close the "underneath" dialog when the "top" dialog opens. Then (if needed), you can re-open the "underneath" dialog when closing the "top" dialog. Seems like a lot of work, but stacking dialogs gets to be tricky with both the code and the UX considerations.
CORONA SDK - facebook.showDialog() appears and disappears after a while
I've added Facebook to my application. The login is ok, except that facebook.login() opens the browser instead of the native Facebook App, but then, when I call Facebook.showDialog() the popup pops up, but after a second or two it disappears. Note that I get this behavior on iOS only, while on Android it is working fine. I'm using the code below in my game.lua file: local function facebookListener( event ) if ( "session" == event.type ) then if ( "login" == event.phase ) then local access_token = event.token facebook.showDialog( "feed", { name = "SuperCool Game Coming soon", description = "Trying to figure out how to get my game to rule the world.", picture = "http://omnigeekmedia.com/wp-content/uploads/2011/05/omniblaster_promo-300x300.png", link = "http://www.omnigeekmedia.com/"}) end elseif ( "request" == event.type ) then print("facebook request") if ( not event.isError ) then local response = json.decode( event.response ) end elseif ( "dialog" == event.type ) then print( "dialog", event.response ) end end fbAppID = "my app ID" --replace with your Facebook App ID function logOnFacebook(event) if(event.phase=="ended")then facebook.login( fbAppID, facebookListener, { "user_friends", "publish_actions" } ) end end ["facebook"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone = true, ["iphone-sim"] = true }, }, I'm running on build 2015.2729. How can I manage to get Facebook work?
I would suggest updating to the v4 version of the plugin. While it's technically still in beta, it's required for iOS 9 builds. See: https://coronalabs.com/blog/2015/07/24/facebook-v4-plugin-android-beta/ https://coronalabs.com/blog/2015/09/01/facebook-v4-plugin-ios-beta-improvements-and-new-features/
Browsing through the source code of the old Facebook plugin, this error will occur in the event that a permission requested wasn't granted! In the source code for the old Facebook plugin, this can be seen in the FBSessionReauthorizeResultHandlers. for ( int i = 0; i < [publishPermissions count]; i++) { if ( ![publishSession.permissions containsObject:[publishPermissions objectAtIndex:i]] ) { release = true; publishError = [[NSError alloc] initWithDomain:#"com.facebook" code:123 userInfo:nil]; break; } } This will also occur for read permissions that were requested and not granted. The issue has since been fixed in the Facebook-v4 plugin.
Drag and drop with touch support for react.js
How to implement drag and drop for Facebooks' react.js with support for touch events? There's a couple of questions and articles and libraries about drag and drop for react.js, but none of them seems to mention touch events, and none of the demo's work on my phone. In general I wonder what would be the easiest: Try to implement this using existing d&d libraries which already support touch, but may need some work to properly work together with react. Or try to use any of the react d&d examples and make them work with touch (which, seeing this issue, may not be trivial?)
react-motion (with touch events) We have tried "react-motion" for dragging items in a list. With more than 15-20 items it becomes really laggy. (But with small list it works good, like in this demo). Be aware that mobile devices are much slower than desktops. Important note about react-motion: Don't forget to use production mode when testing your animation's performance! react-dnd (with touch events) The second option was "react-dnd". It is a great library. It is low level, however, it is rather easy to understand how to work with it. But at first, "react-dnd" was not an option for us because of no touch events support. Later, when Yahoo had released react-dnd-touch-backend we decided to switch our app from "react-motion" to "react-dnd". This solved all our performance issues. We have list 50-70 items and it just works as expected. Yahoo has done really good work and the solution works in our production apps.
You already mentioned react-dnd and I make PR that enable dnd for touch devices so you can try it
I haven't found any answer to this yet. The accepted answer is not really an answer but it points to a github library. I am going to try to include here a complete answer using only react. Here it goes, the code should be self explanatory, but a couple of words ahead of time. We need to use a lot of state variables to keep state between renders, otherwise any variables get reset out. To make the transitions smooth, I update the position once a render was completed using useEffect hook. I tested this in codesandbox, I'm including the link here for anybody to edit the code and play with it, just fork it. It workd with the MS Surface Book2 Pro and Android. It has a formatting problem with the iPhone IOS. Both for Safari and Chrome. If somebody fixes it that'd be great. For now I have what I need and claim success. Here are the files under src in codesandbox.io: App.js import "./styles/index.pcss"; import "./styles/tailwind-pre-build.css"; import Photos from "./Photos.js"; export default function App() { return ( <> <div className="flow-root bg-green-200"> <div className="my-4 bg-blue-100 mb-20"> Drag and Drop with touch screens </div> </div> <div className="flow-root bg-red-200"> <div className="bg-blue-100"> <Photos /> </div> </div> </> ); } Photos.js: import React, { useState } from "react"; import "./styles/index.pcss"; import Image from "./image"; export default function Photos() { const [styleForNumber, setStyleForNumber] = useState({ position: "relative", width: "58px", height: "58px" }); const photosArray = [ "https://spinelli.io/noderestshop/uploads/G.1natalie.1642116451444", "https://spinelli.io/noderestshop/uploads/G.2natalie.1642116452437", "https://spinelli.io/noderestshop/uploads/G.3natalie.1642116453418", "https://spinelli.io/noderestshop/uploads/G.4natalie.1642116454396", "https://spinelli.io/noderestshop/uploads/G.5natalie.1642116455384", "https://spinelli.io/noderestshop/uploads/G.6natalie.1642116456410", "https://spinelli.io/noderestshop/uploads/G.7natalie.1642116457466", "https://spinelli.io/noderestshop/uploads/G.8natalie.1642116458535", "https://spinelli.io/noderestshop/uploads/G.0natalie.1642116228246" ]; return ( <> <div className="w-1/2 bg-green-200" style={{ display: "grid", gridTemplateColumns: "[first] 60px [second] 60px [third] 60px", gridTemplateRows: "60px 60px 60px", rowGap: "10px", columnGap: "20px", position: "relative", justifyContent: "center", placeItems: "center" }} > {photosArray.map((photo, i) => ( <div className="relative z-1 h-full w-full flex flex-wrap content-center touch-none" key={i} > <div className="contents"> <Image photo={photo} i={i} /> </div> </div> ))} </div> </> ); } Image.js: import React, { useRef, useState, useEffect } from "react"; import "./styles/index.pcss"; export default function Image({ photo, i }) { const imgRef = useRef(); const [top, setTop] = useState(0); const [left, setLeft] = useState(0); const [drag, setDrag] = useState(false); const [styleForImg, setStyleForImg] = useState({ position: "absolute", width: "58px", height: "58px" }); const [offsetTop, setOffsetTop] = useState(-40); const [offsetLeft, setOffsetLeft] = useState(0); const [xAtTouchPointStart, setXAtTouchPointStart] = useState(0); const [yAtTouchPointStart, setYAtTouchPointStart] = useState(0); useEffect(() => { if (drag) { setStyleForImg({ position: "relative", width: "58px", height: "58px", top: top, left: left }); } else { setStyleForImg({ position: "relative", width: "58px", height: "58px" }); } console.log("style: ", styleForImg); }, [drag, top, left]); const handleTouchStart = (e, i) => { e.preventDefault(); let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent; let touch = evt.touches[0] || evt.changedTouches[0]; const x = +touch.pageX; const y = +touch.pageY; console.log( "onTouchStart coordinates of icon # start: X: " + x + " | Y: " + y ); console.log("dragged from position n = ", i + 1); // get the mouse cursor position at startup: setXAtTouchPointStart(x); setYAtTouchPointStart(y); setDrag(true); }; const handleTouchEnd = (e) => { // if (process.env.NODE_ENV === 'debug5' || process.env.NODE_ENV === 'development') { e.preventDefault(); setDrag(false); console.log( new Date(), "onTouchEnd event, coordinates of icon # end: X: " + e.changedTouches[0]?.clientX + " | Y: " + e.changedTouches[0]?.clientY + " | top: " + top + " | left: " + left ); }; const handleElementDrag = (e) => { e = e || window.event; e.preventDefault(); let x = 0; let y = 0; //Get touch or click position //https://stackoverflow.com/a/41993300/5078983 if ( e.type === "touchstart" || e.type === "touchmove" || e.type === "touchend" || e.type === "touchcancel" ) { let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent; let touch = evt.touches[0] || evt.changedTouches[0]; x = +touch.pageX; // X Coordinate relative to the viewport of the touch point y = +touch.pageY; // same for Y } else if ( e.type === "mousedown" || e.type === "mouseup" || e.type === "mousemove" || e.type === "mouseover" || e.type === "mouseout" || e.type === "mouseenter" || e.type === "mouseleave" ) { x = +e.clientX; y = +e.clientY; } console.log("x: ", x, "y: ", y); // calculate the new cursor position: const xRelativeToStart = x - xAtTouchPointStart; console.log( "xRel = ", x, " - ", xAtTouchPointStart, " = ", xRelativeToStart ); const yRelativeToStart = y - yAtTouchPointStart; console.log( "yRel = ", y, " - ", yAtTouchPointStart, " = ", yRelativeToStart ); // setXAtTouchPointStart(x); // Reseting relative point to current touch point // setYAtTouchPointStart(y); // set the element's new position: setTop(yRelativeToStart + "px"); setLeft(xRelativeToStart + "px"); console.log("top: ", yRelativeToStart + "px"); console.log("Left: ", xRelativeToStart + "px"); }; const handleDragEnd = (e) => { // if (process.env.NODE_ENV === 'debug5' || process.env.NODE_ENV === 'development') { console.log( new Date(), "Coordinates of icon # end X: " + e.clientX + " | Y: " + e.clientY ); }; const handleDragStart = (e, i) => { // From https://stackoverflow.com/a/69109382/15355839 e.stopPropagation(); // let child take the drag e.dataTransfer.dropEffect = "move"; e.dataTransfer.effectAllowed = "move"; console.log( "Coordinates of icon # start: X: " + e.clientX + " | Y: " + e.clientY ); // console.log ('event: ', e) console.log("dragged from position n = ", i + 1); }; return ( <img ref={imgRef} className="hover:border-none border-4 border-solid border-green-600 mb-4" src={photo} alt="placeholder" style={styleForImg} onDragStart={(e) => handleDragStart(e, i)} onDragEnd={handleDragEnd} onTouchStart={(e) => handleTouchStart(e, i)} onTouchEnd={handleTouchEnd} onTouchMove={handleElementDrag} ></img> ); } index.js: import { StrictMode } from "react"; import ReactDOM from "react-dom"; import "./styles/index.pcss"; import App from "./App"; const root = document.getElementById("root"); ReactDOM.render( <StrictMode> <App /> </StrictMode>, root ); styles.css: .Main { font-family: sans-serif; text-align: center; } /styles/index.pcss: #tailwind base; #tailwind components; #tailwind utilities; I couldn't make tailwinds grid work, so I used the actual css inline styles. No idea why they didn't in codesandbox.
how to avoid validations for optional fields
I created a XFBML form with validations. There are couple of fields which are optional for end user. But as soon as the validations are enabled the form expect all the fields to be filled. So, how to skip required validation for optional fields. The code currently looks like: <fb:registration redirect-uri="http://www.sakshum.org/FbBloodDonorRegister" fields='[{"name":"name"},{"name":"first_name"},{"name":"last_name"}, {"name":"gender"}, {"name":"birthday"},{"name":"email"}, {"name":"cellPhone", "description":"Cell Number", "type":"text"}, {"name":"homePhone", "description":"Home Number", "type":"text"}, {"name":"officePhone", "description":"Office Number", "type":"text"}, {"name":"primaryAddress", "description":"Primary Address", "type":"text"}, {"name":"area", "description":"Locality/Village/Area", "type":"text"},{"name":"location"}]' onvalidate="validated" width="530"> </fb:registration> <script> function validated(form) { errors = {}; if(form.cellPhone.trim().length != 10){ errors.cellPhone = "Cell number is required and should be 10 of digits"; } if(form.homePhone.trim().length > 0){ if(form.homePhone.trim().length != 10) errors.homePhone = "Home number should be 10 of digits"; } if(form.officePhone.trim().length > 0){ if(form.officePhone.trim().length != 10) errors.officePhone = "Office number should be 10 of digits"; } if(form.homePhone.trim().length > 0 || form.officePhone.trim().length > 0){ if(form.homePhone.trim() == form.officePhone.trim() || form.homePhone.trim() == form.cellPhone.trim() || form.officePhone.trim() == form.cellPhone.trim()){ errors.homePhone = "Cell number, office number and home number cannot be same"; errors.officePhone = "Cell number, office number and home number cannot be same"; } } return errors; } </script>
Looks like a known bug to facebook https://developers.facebook.com/bugs/139030226234017?browse=search_50551341bcf073b68986277