User:DannyS712/VueDemo.js

From Meta, a Wikimedia project coordination wiki

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * Demo using Vue and WVUI onwiki
 *
 * @author DannyS712
 */
// <nowiki>
$(() => {
const VueDemo = {};
window.VueDemo = VueDemo;

VueDemo.run = function ( require ) {
	// Don't accidentally lose your work
	window.addEventListener(
		'beforeunload',
		function ( event ) {
			event.returnValue = 'Are you sure you want to close this tab?';
		},
		false
	);
	mw.util.addCSS(`
		#vue-demo-component-ace-wrapper .ace_editor,
		#vue-demo-template-ace-wrapper .ace_editor,
		#vue-demo-styles-ace-wrapper .ace_editor {
		    height: 30em;
		}
	`);
	// Expose for client side testing
	VueDemo.requireFn = require;
	
	var $interactiveWrapper = $( '<div>' ).attr( 'id', 'vue-demo-interactive' );
	$( '#mw-content-text' )
		.empty()
		.append(
			$( '<div>' ).attr( 'id', 'vue-demo-target' ),
			$interactiveWrapper
		);

	var Vue = require( 'vue' );
	var VueCompositionAPI = require( '@vue/composition-api' );
	Vue.use( VueCompositionAPI );
	/**
	Vue.component(
		'demo-vue-component',
		{
			template: `<p>This text came from a component!</p>`
		}
	);
	new Vue( {
		el: '#vue-demo-target',
		components: {
			WvuiButton: require( 'wvui' ).WvuiButton
		},
		data: function () {
			return {
				clicks: 0
			};
		},
		methods: {
			clickHandler: function () {
				console.log( 'Button was clicked!' );
				this.clicks++;
			}
		},
		template: `<div>
			<h2>Stuff created in Vue:</h2>
			<wvui-button v-on:click="clickHandler">WVUI button - increment clicks</wvui-button>
			<wvui-button action="destructive" v-on:click="clicks = 0">WVUI button - reset clicks</wvui-button>
			<demo-vue-component></demo-vue-component>
			<p>The button has been clicked: {{ clicks }} time(s).</p>
		</div>`,
	} );
	*/

	$interactiveWrapper.append(
		$( '<h2>' ).text( 'Live Vue testing:' )
	);
	var $templateInput = $( '<textarea>' )
		.attr( 'id', 'vue-demo-template-input' )
		.attr( 'rows', 10 );
	var $componentInput = $( '<textarea>' )
		.attr( 'id', 'vue-demo-component-input' )
		.attr( 'rows', 10 );
	var $stylesInput = $( '<textarea>' )
		.attr( 'id', 'vue-demo-styles-input' )
		.attr( 'rows', 10 );
	var $updateButton = $( '<button>' )
		.attr( 'id', 'vue-demo-update-result' )
		.text( 'Update output' );
	var $result = $( '<div>' ).attr( 'id', 'vue-demo-live-result' );
	$interactiveWrapper.append(
		$( '<label>' ).attr( 'for', 'vue-demo-template-input' ).text( 'Template input: (wrapped in a div, so no need to worry about only one root element)' ),
		$( '<div>' ).attr( 'id', 'vue-demo-template-ace-wrapper' ).append(
			$templateInput
		),
		$( '<label>' ).attr( 'for', 'vue-demo-component-input' ).text( 'Component input: (wvui components are registered globally, and the composition api can be accessed using the global `VueCompositionAPI` variable)' ),
		$( '<div>' ).attr( 'id', 'vue-demo-component-ace-wrapper' ).append(
			$componentInput
		),
		$( '<label>' ).attr( 'for', 'vue-demo-styles-input' ).text( 'Styles input (CSS):' ),
		$( '<div>' ).attr( 'id', 'vue-demo-styles-ace-wrapper' ).append(
			$stylesInput
		),
		$updateButton,
		$( '<hr>' ),
		$( '<label>' ).attr( 'for', 'vue-demo-live-result' ).text( 'Result:' ),
		$result
	);
	
	// Initial values, for testing and demo
	$templateInput.val( `<h2>Stuff created in Vue:</h2>
<h4>Options API example</h4>
<wvui-button v-on:click="optionsClickHandler">WVUI button - increment clicks (options)</wvui-button>
<wvui-button action="destructive" v-on:click="optionsClicks = 0">WVUI button - reset clicks (options)</wvui-button>
<p class="blue-text">The options button has been clicked: {{ optionsClicks }} time(s).</p>
<h4>Composition API example</h4>
<wvui-button v-on:click="compositionClickHandler">WVUI button - increment clicks</wvui-button>
<wvui-button action="destructive" v-on:click="compositionClicks = 0">WVUI button - reset clicks (composition)</wvui-button>
<p class="blue-text">The composition button has been clicked: {{ compositionClicks }} time(s).</p>` );
	$componentInput.val( `module.exports = {
    data: function () {
        return {
            optionsClicks: 0
        };
    },
	methods: {
		optionsClickHandler: function () {
			console.log( 'Options button was clicked!' );
			this.optionsClicks++;
		}
	},
	setup( props ) {
	    const compositionClicks = VueCompositionAPI.ref( 0 );
	    const compositionClickHandler = function () {
	        console.log( 'Composition button was clicked!' );
	        compositionClicks.value++;
	    };
	    return { compositionClicks, compositionClickHandler };
	}
};` );
	$stylesInput.val( `.blue-text {
	color: blue;
}` );
	
	// Set up ACE
	var ACEcomponent = ace.edit( 'vue-demo-component-input' );
	ACEcomponent.session.setMode('ace/mode/javascript');
	var ACEtemplate = ace.edit( 'vue-demo-template-input' );
	ACEtemplate.session.setMode('ace/mode/html');
	var ACEstyles = ace.edit( 'vue-demo-styles-input' );
	ACEstyles.session.setMode('ace/mode/css');
	
	// Filter out warnings about a missing DOCTYPE tag, this isn't a full HTML
	// document but rather just a small part that is embedded (its actually not even
	// HTML but that is the closest language ACE has)
	// Attribution: https://stackoverflow.com/questions/33232632/how-can-i-remove-the-first-doctype-tooltip-of-the-ace-editor-in-my-html-editor
	ACEtemplate.session.on('changeAnnotation', function () {
		if ( !ACEtemplate.session.$annotations ) {
			// No annotations to filter
			return;
		}
		ACEtemplate.session.$annotations = ACEtemplate.session.$annotations.filter(function(annotation){
			return !(/doctype first\. Expected/.test(annotation.text) || /Unexpected End of file\. Expected/.test(annotation.text));
		});
		ACEtemplate.$onChangeAnnotation();
	});

	const styleTag = mw.loader.addStyleTag( '' );
	// Export for debugging
	VueDemo.ACEcomponent = ACEcomponent;
	VueDemo.ACEtemplate = ACEtemplate;
	VueDemo.ACEstyles = ACEstyles;
	VueDemo.styleTag = styleTag;
	
	// Expose to testing components (wvui can be used directly, but it is
	// also registered globally so you don't really need to)
	window.wvui = require( 'wvui' );
	window.VueCompositionAPI = VueCompositionAPI;
	VueDemo.currentApp = false;
	VueDemo.renderComponent = function () {
		// Unmount old version
		if ( VueDemo.currentApp !== false ) {
			VueDemo.currentApp.unmount();
			VueDemo.currentApp = false;
		}
		// Remove any existing rendering, and add an element to replace
		$result.empty();
		$result.append(
			$( '<div>' ).attr( 'id', 'vue-demo-result-replace' )
		);
		try {
			// Component input should set module.exports, set up module object
			// and then return the exports
			var compF = new Function( 'var module={};' + ACEcomponent.getValue() + ';return module.exports;' );
			var comp = compF();
			const templateField = '<div>' + ACEtemplate.getValue() + '</div>';
			if ( comp.template === undefined || comp.template === '' ) {
				comp.template = templateField;
			}
			VueDemo.currentApp = Vue.createMwApp( comp );
			// register WVUI components as globally available
			Object.keys( wvui ).forEach( ( componentName ) => {
				VueDemo.currentApp.component( componentName, wvui[ componentName ] );
			} );
			VueDemo.currentInstance = VueDemo.currentApp.mount( '#vue-demo-result-replace' );
			styleTag.innerText = ACEstyles.getValue();
		} catch ( e ) {
			$result.append(
				$( '<b>' ).text( 'Something went wrong, check the console' )
			);
			console.error( e );
		}
	};
	
	$updateButton.on( 'click', VueDemo.renderComponent );
};

});

$( document ).ready(
	function () {
		if ( mw.config.get( 'wgPageName' ) === "Special:BlankPage/VueDemo" ) {
			mw.loader.load( '//ajaxorg.github.io/ace-builds/src-min/ace.js' );
			mw.loader.using(
				[ 'vue', 'wvui', 'mediawiki.util', '@vue/composition-api' ],
				window.VueDemo.run
			);
		}
	}
);
// </nowiki>