We are trying to migrate the entire project to Vue3, so there were lots of rewrite, including basic components, from button to message, dialog, etc, and all the business logic and related components.
One component we need to rewrite was vue-social-sharing
, which is really easy to use in Vue2.
Dissecting the source code
when we dig into what inside source code of vue-social-sharing
, we can find three source files :
- vue-social-sharing.js
- network.js
- share-network.js
well, the first one vue-social-sharing.js
is the entry file where it instructs how to mount the component in the correct way:
well, this is easy to write to Vue3, and we can also find a example here:
just a simple Vue.use()
, easy to understand, lets check other files
in network.js
, it just exports bunch of social media links:
we can keep that, no need to change anything
now the remaining issue is share-network.js
:
import AvailableNetworks from './networks'
let $window = typeof window !== 'undefined' ? window : null
export function mockWindow (self) {
$window = self || window // mock window for unit testing
}
export default {
name: 'ShareNetwork',
props: {
/**
* Name of the network to display.
*/
network: {
type: String,
required: true
},
/**
* URL of the content to share.
*/
url: {
type: String,
required: true
},
/**
* Title of the content to share.
*/
title: {
type: String,
required: true
},
/**
* Description of the content to share.
*/
description: {
type: String,
default: ''
},
/**
* Quote content, used for Facebook.
*/
quote: {
type: String,
default: ''
},
/**
* Hashtags, used for Twitter and Facebook.
*/
hashtags: {
type: String,
default: ''
},
/**
* Twitter user, used for Twitter
* @var string
*/
twitterUser: {
type: String,
default: ''
},
/**
* Media to share, used for Pinterest
*/
media: {
type: String,
default: ''
},
/**
* HTML tag used by the Network component.
*/
tag: {
type: String,
default: 'a'
},
/**
* Properties to configure the popup window.
*/
popup: {
type: Object,
default: () => ({
width: 626,
height: 436
})
}
},
data () {
return {
popupTop: 0,
popupLeft: 0,
popupWindow: undefined,
popupInterval: null
}
},
computed: {
/**
* List of available networks
*/
networks () {
return this.$SocialSharing
? this.$SocialSharing.options.networks
: AvailableNetworks
},
/**
* Formatted network name.
*/
key () {
return this.network.toLowerCase()
},
/**
* Network sharing raw sharing link.
*/
rawLink () {
const ua = navigator.userAgent.toLowerCase()
/**
* On IOS, SMS sharing link need a special formatting
* Source: https://weblog.west-wind.com/posts/2013/Oct/09/Prefilling-an-SMS-on-Mobile-Devices-with-the-sms-Uri-Scheme#Body-only
*/
if (this.key === 'sms' && (ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1)) {
return this.networks[this.key].replace(':?', ':&')
}
return this.networks[this.key]
},
/**
* Create the url for sharing.
*/
shareLink () {
let link = this.rawLink
/**
* Twitter sharing shouldn't include empty parameter
* Source: https://github.com/nicolasbeauvais/vue-social-sharing/issues/143
*/
if (this.key === 'twitter') {
if (!this.hashtags.length) link = link.replace('&hashtags=@h', '')
if (!this.twitterUser.length) link = link.replace('@tu', '')
}
return link
.replace(/@tu/g, '&via=' + encodeURIComponent(this.twitterUser))
.replace(/@u/g, encodeURIComponent(this.url))
.replace(/@t/g, encodeURIComponent(this.title))
.replace(/@d/g, encodeURIComponent(this.description))
.replace(/@q/g, encodeURIComponent(this.quote))
.replace(/@h/g, this.encodedHashtags)
.replace(/@m/g, encodeURIComponent(this.media))
},
/**
* Encoded hashtags for the current social network.
*/
encodedHashtags () {
if (this.key === 'facebook' && this.hashtags.length) {
return '%23' + this.hashtags.split(',')[0]
}
return this.hashtags
}
},
render: function (createElement) {
if (!this.networks.hasOwnProperty(this.key)) {
throw new Error('Network ' + this.key + ' does not exist')
}
const node = {
class: 'share-network-' + this.key,
on: {
click: () => this[this.rawLink.substring(0, 4) === 'http' ? 'share' : 'touch']()
}
}
if (this.tag === 'a') node.attrs = { href: 'javascript:void(0)' }
return createElement(this.tag, node, this.$slots.default)
},
methods: {
/**
* Center the popup on multi-screens
* http://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen/32261263
*/
resizePopup () {
const width = $window.innerWidth || (document.documentElement.clientWidth || $window.screenX)
const height = $window.innerHeight || (document.documentElement.clientHeight || $window.screenY)
const systemZoom = width / $window.screen.availWidth
this.popupLeft = (width - this.popup.width) / 2 / systemZoom + ($window.screenLeft !== undefined ? $window.screenLeft : $window.screenX)
this.popupTop = (height - this.popup.height) / 2 / systemZoom + ($window.screenTop !== undefined ? $window.screenTop : $window.screenY)
},
/**
* Shares URL in specified network.
*/
share () {
this.resizePopup()
// If a popup window already exist, we close it and trigger a change event.
if (this.popupWindow && this.popupInterval) {
clearInterval(this.popupInterval)
// Force close (for Facebook)
this.popupWindow.close()
this.emit('change')
}
this.popupWindow = $window.open(
this.shareLink,
'sharer-' + this.key,
',height=' + this.popup.height +
',width=' + this.popup.width +
',left=' + this.popupLeft +
',top=' + this.popupTop +
',screenX=' + this.popupLeft +
',screenY=' + this.popupTop
)
// If popup are prevented (AdBlocker, Mobile App context..), popup.window stays undefined and we can't display it
if (!this.popupWindow) return
this.popupWindow.focus()
// Create an interval to detect popup closing event
this.popupInterval = setInterval(() => {
if (!this.popupWindow || this.popupWindow.closed) {
clearInterval(this.popupInterval)
this.popupWindow = null
this.emit('close')
}
}, 500)
this.emit('open')
},
/**
* Touches network and emits click event.
*/
touch () {
window.open(this.shareLink, '_blank')
this.emit('open')
},
emit (name) {
this.$root.$emit('share_network_' + name, this.key, this.url)
this.$emit(name, this.key, this.url)
}
}
}
when we are wondering what is inside this
, lets just print it out
here it shows it is a VueComponent instance, we can rewrite methods, computed property and most of others to Vue3 style first, and in the end, let's take a look of the render
function
Rewrite
methods
- to
function
or arrow functions
variables
- use
ref
orreactive
to define
props
- use
defineProps
to define props - to destructure props, use
toRefs
ortoRef
emits
- use
defineEmits
to define emit
computed
- use
computed(() => xxx)
to define computed properties
watch
- use
watch(() => props.xxx, (newVal, oldVal) => xxxxxx)
to watch for certain property -
watchEffect
is another thing we can use
Render Function
First, let's read through render function topic in both Vue2 and Vue3 doc:
here we can see that in Vue2 the render function has a default template:
in Vue2:
-
createElement
is actually a function, can print it out and take a look - render function eventually returns
createElement
-
createElement
can pass 3 args- 1st one required, String/Object/Function
- 2nd optional, Object
- 3rd optional, String/Array
in Vue3, there are major changes, actually, I think the doc explains very clearly about changes, so I will just proceed to the following part.
Rewrite Render Logic in Vue3
now let's write our share-network.js
here:
import AvailableNetworks from './network';
import {
ref,
computed,
h,
onMounted,
reactive,
toRefs,
getCurrentInstance
} from 'vue';
let $window = typeof window !== 'undefined' ? window : null;
export default {
name: 'DPSharing',
props: {
network: {
type: String,
required: true
},
url: {
type: String,
required: true
},
title: {
type: String,
required: true
},
description: {
type: String,
default: ''
},
quote: {
type: String,
default: ''
},
hashtags: {
type: String,
default: ''
},
twitterUser: {
type: String,
default: ''
},
media: {
type: String,
default: ''
},
tag: {
type: String,
default: 'a'
},
popup: {
type: Object,
default: () => ({
width: 626,
height: 436
})
}
},
emits: ['change', 'close', 'open'],
setup(props, context) {
const { slots, emit } = context;
const popupTop = ref(0);
const popupLeft = ref(0);
const popupWindow = ref(undefined);
const popupInterval = ref(null);
const {
network,
url,
title,
description,
quote,
hashtags,
twitterUser,
media,
tag,
popup
} = toRefs(props);
const networks = computed(() => {
return AvailableNetworks;
});
const key = computed(() => {
return network.value.toLowerCase();
});
const rawLink = computed(() => {
const ua = navigator.userAgent.toLowerCase();
if (
key.value === 'sms' &&
(ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1)
) {
return networks.value[key.value].replace(':?', ':&');
}
return networks.value[key.value];
});
const shareLink = computed(() => {
console.log('check sharelink');
let link = rawLink.value;
if (key.value === 'twitter') {
if (!hashtags.value.length) link = link.replace('&hashtags=@h', '');
if (!twitterUser.value.length) link = link.replace('@tu', '');
}
return link
.replace(/@tu/g, '&via=' + encodeURIComponent(twitterUser.value))
.replace(/@u/g, encodeURIComponent(url.value))
.replace(/@t/g, encodeURIComponent(title.value))
.replace(/@d/g, encodeURIComponent(description.value))
.replace(/@q/g, encodeURIComponent(quote.value))
.replace(/@h/g, encodedHashtags.value)
.replace(/@m/g, encodeURIComponent(media));
});
const encodedHashtags = computed(() => {
console.log('check encodedHashtags');
if (key === 'facebook' && hashtags.value.length) {
return '%23' + hashtags.value.split(',')[0];
}
return hashtags.value;
});
const resizePopup = () => {
console.log('resize popup method triggered here ');
const width =
$window.innerWidth ||
document.documentElement.clientWidth ||
$window.screenX;
const height =
$window.innerHeight ||
document.documentElement.clientHeight ||
$window.screenY;
const systemZoom = width / $window.screen.availWidth;
popupLeft.value =
(width - popup.width) / 2 / systemZoom +
($window.screenLeft !== undefined
? $window.screenLeft
: $window.screenX);
popupTop.value =
(height - popup.height) / 2 / systemZoom +
($window.screenTop !== undefined ? $window.screenTop : $window.screenY);
};
const share = () => {
// debugger;
resizePopup();
// If a popup window already exist, we close it and trigger a change event.
if (popupWindow.value) {
clearInterval(popupInterval.value);
// Force close (for Facebook)
popupWindow.value.close();
emit('change');
}
popupWindow.value = $window.open(
shareLink.value,
'sharer-' + key.value,
',height=' +
popup.value.height +
',width=' +
popup.value.width +
',left=' +
popupLeft.value +
',top=' +
popupTop.value +
',screenX=' +
popupLeft.value +
',screenY=' +
popupTop.value
);
// If popup are prevented (AdBlocker, Mobile App context..), popup.window stays undefined and we can't display it
if (!popupWindow.value) return;
popupWindow.value.focus();
// Create an interval to detect popup closing event
popupInterval.value = setInterval(() => {
if (!popupWindow.value || popupWindow.value.closed) {
clearInterval(popupInterval.value);
popupWindow.value = null;
emit('close');
}
}, 500);
emit('open');
};
const touch = () => {
console.log('touch method triggered');
window.open(shareLink.value, '_blank');
emit('open');
};
const renderData = () => {
if (!networks.value.hasOwnProperty(key.value)) {
throw new Error('Network ' + key.value + ' does not exist');
}
const node = {
class: 'share-network-' + key.value,
on: {
click: rawLink.value.substring(0, 4) === 'http' ? share : touch
},
attrs: {
href:
rawLink.value.substring(0, 4) === 'http'
? shareLink.value
: rawLink.value
}
};
if (tag.value === 'a') {
node.attrs = { href: 'javascript:void(0)' };
}
return [tag.value, node, slots.default()];
};
const data = renderData();
const tg = `${data[0]}`;
const node = data[1];
const content = data[2];
return () =>
h(
tg,
{
onClick: node.on.click,
href: node.attrs.href
},
content
);
}
};
pay attention to the render function we return in the end, we first identify a function called renderData
, which returns data for rendering purpose, and console.log()
it to show the response:
{
"a",
{
"class": "share-network-facebook",
"on": {},
"attrs": {
"href": "javascript:void(0)"
}
}
}
the a
tag is
and you can export that and register your component to use
example:
<COSharing network="facebook" :title="'facebook'" url="https://www.google.com" quote="hello" >
click me to open whatsapp sharing
</COSharing>
quote
is for twitter
and when you inspect your element, you will find element wrapped in a
tag:
<a href="javascript:void(0)" data-v-5be083fa="">
// whatever your content here
</a>
and now we have a successful rewrite into Vue3 for vue-social-sharing
library, do not need to rely on the update of that package anymore, you can learn to create your own, which is actually a good practice
Top comments (0)