Wiris MathType does not parse MathML on Vue Component reload - vue.js

I am working on a Vue JS app which has MathType plugin and the plugin works fine when the formula is entered and it is displayed in the correct format. But when the component is destroyed and created again and the previously entered data is passed into the component using v-model, the MathML code is intact (as observed in Vue inspector) but only the raw text content is shown in the editor and the equations are not parsed to the image format. If I reload the entire page with the v-model containing formulae, MathType parses it and displays the correct images. Is there a way to trigger this parsing for dynamically/programatically added text in CKEditor 4? Here is my code:
<template>
<div>
<input v-bind:name="fieldName" type="hidden" v-model="content" />
<ckeditor v-bind:editor-url="editorUrl" ref="editor" v-bind:config="editorConfig" v-model="content"></ckeditor>
</div>
</template>
<script>
import CKEditor from 'ckeditor4-vue';
export default {
props: {
currentContent: {
type: String,
default: null,
},
fieldName: {
type: String,
default: 'content',
},
toolbarType: {
type: String,
default: 'Full',
},
},
data: function () {
return {
editorConfig: {
embed_provider: '//ckeditor.iframe.ly/api/oembed?url={url}&callback={callback}',
mathJaxLib: '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML',
protectedSource: [/<u[^>]><\/u>/g],
toolbar_Exam: [
['Cut', 'Copy', 'Paste', 'PasteFromText', 'PasteFromWord', 'Undo', 'Redo'],
['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'],
['Outdent', 'Indent', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
['Table', 'HorizontalRule', 'Smiley', 'SpecialChar'],
['Styles', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor'],
//['oembed', 'MediaEmbed'],
['ckeditor_wiris_formulaEditor', 'ckeditor_wiris_formulaEditorChemistry'],
],
toolbar_Full: null,
toolbar: this.toolbarType,
allowedContent: true,
extraAllowedContent: 'math[xmlns]; maction; maligngroup; malignmark; menclose; merror; mfenced; mfrac; mglyph; mi;' +
'mlabeledtr; mlogdiv; mmultiscripts; mn; mo; mover; mpadded; mphantom; mroot; mrow; ms; mscarries; mscarry;' +
'msgroup; mstack; msline; mspace; msqrt; msrow; mstack; mstyle; msub; msup; msubsup; mtable; mtd; mtext; mtr;' +
'munder; munderover; semantics; annotation; annotation-xml;',
},
editorUrl: siteUrl+'/vendor/ckeditor/ckeditor.js',
};
},
components: {
'ckeditor': CKEditor.component,
},
};
</script>

I have fixed this problem by using the Generic Wiris MathType Plugin https://www.npmjs.com/package/#wiris/mathtype-generic and importing the wirisplugin-generic.js script and using the WirisPlugin.Parser.initParse on the content variable in the component in the mounted function.
mounted() {
this.content = WirisPlugin.Parser.initParse(this.content);
}
And this has resolved the issue.

Related

Custom locale in vis.js timeline using vue.js

I am using vis.js timeline in a Vue.js project. Although it seems pretty easy to customize the locale using vanilla javascript (see codepen and documentation), I just can't get it done with Vue. I have already added moment.js and moment-with-locales-es6 to my project. Is this the right way to apply the locale to moment.js so it also applies to vis timeline?
This is my vue.js component:
<template>
<div className="wrapper">
<div id="visualization"></div>
</div>
</template>
<script>
import {Timeline} from 'vis-timeline/standalone';
import tr from 'moment/locale/tr'
import moment from "moment-with-locales-es6";
export default {
data() {
return {
items: [
{
id: 1,
content: "1",
start: new Date(2021, 2, 23),
group: 0
},
options: {
locale: "tr",
locales: {
tr: {
current: "geçerli",
time: "kere",
},
},
},
timeline: null
}
},
mounted() {
this.timeline = new Timeline(document.getElementById('visualization'));
this.timeline.setOptions(this.options);
this.timeline.setItems(this.items);
moment.locale('tr')
}
}
</script>
I managed to do it by adding this to my index.html file:
<script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/locale/tr.js'></script>
I am pretty sure there's a better solution for this...

Create dynamic Google Map Markers with SVGs

I am trying to create Google Map Markers with dynamic information in it. For that feature I am using a Vuejs-Component with a SVG as template and add a "text"-Tag.
Example:
<template>
<svg width="40px" height="56px" viewBox="0 0 40 56" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<text>{{ text }}</text>
</svg>
</template>
<script>
export default {
name: "DynamicGMapMarker",
props: {
text: {
type: String,
required: true
}
}
}
</script>
Then I am creating the SVG via a helper function and create a base64 data url:
import Vue from 'vue'
import DynamicGMapMarker from './DynamicGMapMarker'
const DynamicGMapMarkerConstructor = Vue.extend(DynamicGMapMarker)
export const getDynamicMarkerIcon = (text) => {
const iconComponent = new DynamicGMapMarkerConstructor({
propsData: {
text
}
});
iconComponent.$mount();
const iconDom = iconComponent.$el;
const iconString = new XMLSerializer().serializeToString(iconDom);
return 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(iconString);
}
Now, I can use the getDynamicMarkerIcon() function. This is working in vue.js, but not in nuxt.js. In nuxt.js we need to add a XML-Serializer from npm, since nuxt can not use the Browser's XMLSerializer. For that I am using teclone/xml-serializer.
Now, the Problem seems to be that Nuxt can not create a DOM, because of SSR. When I am trying to use this method:
<template>
<div id="app">
<GmapMap
:center="center"
:zoom="15"
:options="options"
id="map"
>
<GmapCluster :zoomOnClick="true">
<GmapMarker
:key="index"
v-for="(marker, index) in markers"
:position="marker.coordinates"
:clickable="true"
:draggable="false"
:title="marker.name"
:icon="getMarker(marker)"
/>
</GmapCluster>
</GmapMap>
</div>
</template>
<script>
import GmapCluster from 'vue2-google-maps/dist/components/cluster'
import {getMarkerIcon} from "./MapIconUtil"
export default {
name: 'App',
components: {
GmapCluster
},
methods: {
getMarker(marker) {
return getDynamicMarkerIcon(marker.text);
}
},
data() {
return {
options: {
zoomControl: false,
mapTypeControl: false,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
disableDefaultUi: true,
clickableIcons: false
},
center: {
lat: 11,
lng: 11
},
"markers": [
{
coordinates: {
name: "Test",
lat: 11.0001,
lng: 11.0001,
text: "101"
}
}
]
}
}
}
</script>
I get an error that iconComponent.$el is undefined. I can not mount the SVG inside a DOM.
Are there any possibilities to tell Nuxt, that this component should run on browser side only?
I have already tried to wrap GmapMarker in <client-only>-Tag. That did not work. Can I use something like a "virtual DOM"? Any other ideas how to make it work?
I did it. You can selectively change the "mode" in which nuxt will render a specific url. Here you can see what configurations are possible.
You have to create a server middleware and tell nuxt if a path should be rendered in spa. Otherwise just default to your mode specified in your nuxt.config.js.
Check this out for an example.

Stripe is not defined in Nuxt

I want to add stripe elements to my nuxt js page However, I got an error
Stripe is not defined
I have inserted the <script src="https://js.stripe.com/v3/"></script> on my nuxt.config.js
Here's the code
head: {
title: process.env.npm_package_name || "",
script: [{ src: "https://js.stripe.com/v3/" }],
link: [
{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
]
},
My payment page
<template>
<div>
<div ref="card"></div>
<button v-on:click="purchase">Purchase</button>
</div>
</template>
<script>
let stripe = Stripe("Key"),
elements = stripe.elements(),
card = undefined;
export default {
mounted: function() {
card = elements.create("card");
card.mount(this.$refs.card);
},
methods: {
async purchase() {
let result = await stripe.createToken(card);
}
}
};
</script>
I followed this tutorial and still can't fix it
https://alligator.io/vuejs/stripe-elements-vue-integration/
You are including stripe for into scripts, so it will be loaded in browser. But nuxt is SSR. And the code in your script section will be also executed on server. And on server there no stripe, so it wont work. You need to execute all your code that create Stripe in mounted hook, which is only executed on client

Pulling in several static properties into Vue.js

I am very new to Vue.js and we are working on adding in Vue.js into an existing project piece by piece. I'm working on rewriting the product slider in Vue. It is currently using the jquery slick slider. So in the current/old code in the html this js function is being called:
function productDetailsRecommendations(compositeNumbers) {
var params = {
compositeNumbers: compositeNumbers,
strategy: 'pp12',
backupStrategy: 'popular',
divId: 'recommendedProductsHorizontal',
isVertical: false,
isHideHeaderText: false,
headerText: 'Guests Who Viewed This Item Also Viewed These',
backupHeaderText: 'Popular Products',
itemsPerPage: 5,
itemDisplayLimit: 10,
numberOfItems: 15,
responseMap: null
};
createSlider(params);
}
Now I am using vue-carousel to recreate the slider. So I replaced that call with my own copied function: productDetailsRecommendationsVue.
Now I have created a ProductRecommendationsSlider.vue as the slider component. And I have a index.js as the entry point where the slider gets initialized.
Now my boss told me I need to put the productDetailsRecommendationsVue function into index.js.
// index.js
import Vue from 'vue';
import axios from 'axios';
import VueCarousel from 'vue-carousel';
import Slider from '/components/slider/ProductRecommendationsSlider'
Vue.use(VueCarousel);
window.productDetailsRecommendationsVue=function(compositeNumbers) {
var params = {
compositeNumbers: compositeNumbers,
strategy: 'pp12',
backupStrategy: 'popular',
divId: 'recommendedProductsHorizontal',
isVertical: false,
isHideHeaderText: false,
headerText: 'Guests Who Viewed This Item Also Viewed These',
backupHeaderText: 'Popular Products',
itemsPerPage: 5,
itemDisplayLimit: 10,
numberOfItems: 15,
responseMap: null
};
};
/* eslint-disable no-new */
new Vue({
el: '#itemDetailPage #recommendedProductsHorizontal .imageSlider',
components: {
Slider,
'carousel': VueCarousel.Carousel,
'slide': VueCarousel.Slide
},
template: '<product-slider></product-slider>'
});
But my main question is how do I get those parameters into the component?
They are needed in one of the functions in ProductRecommendationsSlider.vue. My boss said I was on the right track with placing the js function there in the index.js. All the tutorials I've found online talk about building a project from scratch. Tying Vue into an existing project is much more difficult IMO.
Since you're using single file components (*.vue within a Vue CLI generated project), your project already has modularization support, so you wouldn't need to attach properties/functions to the window object. Instead, you could encapsulate your static properties/functions within the component file itself:
// ProductRecommendationsSlider.vue
<script>
function productDetailsRecommendations() {
return { /*...*/ }
}
export default {
data() {
params: {}
},
methods: {
loadParams() {
this.params = productDetailsRecommendations();
}
}
}
</script>
or in separate files that you could import into your component:
// ProductRecommendationsSlider.vue
<script>
import { productDetailsRecommendations } from '#/utils';
export default {
data() {
params: {}
},
methods: {
loadParams() {
this.params = productDetailsRecommendations();
}
}
}
</script>
// <root>/src/utils.js
export function productDetailsRecommendations() {
return { /*...*/ }
}
Then, you could bind those parameters to your vue-carousel properties. Note only some of the parameters in your example appear to be supported by vue-carousel (unsupported marked by n/a):
"strategy": "pp12", // n/a
"backupStrategy": "popular", // n/a
"divId": "recommendedProductsHorizontal", // ID of container div
"isVertical": false, // n/a
"isHideHeaderText": false, // true = hide `headerText` h3; false = show it
"headerText": "Guests Who Viewed This Item Also Viewed These", // h3 text content (isHideHeaderText: true)
"backupHeaderText": "Popular Products", // h3 text content (isHideHeaderText: false)
"itemsPerPage": 5, // vue-carousel perPage
"itemDisplayLimit": 10, // n/a
"numberOfItems": 15, // vue-carousel item count
"responseMap": null // n/a
Example data bindings:
<template>
<div class="product-slider" :id="params.recommendedProductsHorizontal">
<h3 v-if="!params.isHideHeaderText">{{params.headerText}}</h3>
<carousel :perPage="params.itemsPerPage">
<slide v-for="i in params.numberOfItems" :key="i">
<span class="label">{{i}}</span>
</slide>
</carousel>
<section>
<button #click="loadParams">Load params</button>
<pre>params: {{params}}</pre>
</section>
</div>
</template>
demo
You can assign window.productDetailsRecommendationVue in vue data or computed properties
1) Change window.productDetailsRecommendationsVue from a function to
window.productDetailsRecommendationsVue = {
//compositeNumbers: "I have no idea where this comes from but it could be passed separately",
strategy: "pp12",
backupStrategy: "popular",
divId: "recommendedProductsHorizontal",
isVertical: false,
isHideHeaderText: false,
headerText: "Guests Who Viewed This Item Also Viewed These",
backupHeaderText: "Popular Products",
itemsPerPage: 5,
itemDisplayLimit: 10,
numberOfItems: 15,
responseMap: null
};
2) inside of your vue instance of index.js assign window.productDetailsRecommendtionsVue to a data property:
new Vue({
el: '#itemDetailPage #recommendedProductsHorizontal .imageSlider',
components: {
Slider,
'carousel': VueCarousel.Carousel,
'slide': VueCarousel.Slide
},
data: {
oldSliderData: window.productDetailsRecommendationsVue
}
template: '<product-slider></product-slider>'
});
It is now accessible to components using the standard prop process. I'm not sure where is coming from b/c I don't see it imported.

Dynamic Vue components with sync and events

I'm using <component v-for="..."> tags in Vue.js 2.3 to dynamically render a list of components.
The template looks like this:
<some-component v-for="{name, props}, index in modules" :key="index">
<component :is="name" v-bind="props"></component>
</some-component>
The modules array is in my component data() here:
modules: [
{
name: 'some-thing',
props: {
color: '#0f0',
text: 'some text',
},
},
{
name: 'some-thing',
props: {
color: '#f3f',
text: 'some other text',
},
},
],
I'm using the v-bind={...} object syntax to dynamically bind props and this works perfectly. I also want to bind event listeners with v-on (and use .sync'd props) with this approach, but I don't know if it's possible without creating custom directives.
I tried adding to my props objects like this, but it didn't work:
props: {
color: '#f3f',
text: 'some other text',
'v-on:loaded': 'handleLoaded', // no luck
'volume.sync': 'someValue', // no luck
},
My goal is to let users re-order widgets in a sidebar with vuedraggable, and persist their layout preference to a database, but some of the widgets have #events and .synced props. Is this possible? I welcome any suggestions!
I don't know of a way you could accomplish this using a dynamic component. You could, however, do it with a render function.
Consider this data structure, which is a modification of yours.
modules: [
{
name: 'some-thing',
props: {
color: '#0f0',
text: 'some text',
},
sync:{
"volume": "volume"
},
on:{
loaded: "handleLoaded"
}
},
{
name: 'other-thing',
on:{
clicked: "onClicked"
}
},
],
Here I am defining two other properties: sync and on. The sync property is an object that contains a list of all the properties you would want to sync. For example, above the sync property for one of the components contains volume: "volume". That represents a property you would want to typically add as :volume.sync="volume". There's no way (that I know of) that you can add that to your dynamic component dynamically, but in a render function, you could break it down into it's de-sugared parts and add a property and a handler for updated:volume.
Similarly with the on property, in a render function we can add a handler for an event identified by the key that calls a method identified in the value. Here is a possible implementation for that render function.
render(h){
let components = []
let modules = Object.assign({}, this.modules)
for (let template of this.modules) {
let def = {on:{}, props:{}}
// add props
if (template.props){
def.props = template.props
}
// add sync props
if (template.sync){
for (let sync of Object.keys(template.sync)){
// sync properties are just sugar for a prop and a handler
// for `updated:prop`. So here we add the prop and the handler.
def.on[`update:${sync}`] = val => this[sync] = val
def.props[sync] = this[template.sync[sync]]
}
}
// add handers
if (template.on){
// for current purposes, the handler is a string containing the
// name of the method to call
for (let handler of Object.keys(template.on)){
def.on[handler] = this[template.on[handler]]
}
}
components.push(h(template.name, def))
}
return h('div', components)
}
Basically, the render method looks through all the properties in your template in modules to decide how to render the component. In the case of properties, it just passes them along. For sync properties it breaks it down into the property and event handler, and for on handlers it adds the appropriate event handler.
Here is an example of this working.
console.clear()
Vue.component("some-thing", {
props: ["volume","text","color"],
template: `
<div>
<span :style="{color}">{{text}}</span>
<input :value="volume" #input="$emit('update:volume', $event.target.value)" />
<button #click="$emit('loaded')">Click me</button>
</div>
`
})
Vue.component("other-thing", {
template: `
<div>
<button #click="$emit('clicked')">Click me</button>
</div>
`
})
new Vue({
el: "#app",
data: {
modules: [{
name: 'some-thing',
props: {
color: '#0f0',
text: 'some text',
},
sync: {
"volume": "volume"
},
on: {
loaded: "handleLoaded"
}
},
{
name: 'other-thing',
on: {
clicked: "onClicked"
}
},
],
volume: "stuff"
},
methods: {
handleLoaded() {
alert('loaded')
},
onClicked() {
alert("clicked")
}
},
render(h) {
let components = []
let modules = Object.assign({}, this.modules)
for (let template of this.modules) {
let def = {
on: {},
props: {}
}
// add props
if (template.props) {
def.props = template.props
}
// add sync props
if (template.sync) {
for (let sync of Object.keys(template.sync)) {
// sync properties are just sugar for a prop and a handler
// for `updated:prop`. So here we add the prop and the handler.
def.on[`update:${sync}`] = val => this[sync] = val
def.props[sync] = this[template.sync[sync]]
}
}
// add handers
if (template.on) {
// for current purposes, the handler is a string containing the
// name of the method to call
for (let handler of Object.keys(template.on)) {
def.on[handler] = this[template.on[handler]]
}
}
components.push(h(template.name, def))
}
return h('div', components)
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app"></div>