-
-
-
-
- {{ message[0] }}
- {{ message[1] }}
-
-
-
+
+
+
+
+ {{
+ message[0] === 'danger' ? 'error' : 'log'
+ }}
+ {{ message[1] }}
+
+
+
@@ -154,7 +144,19 @@
@@ -163,25 +165,43 @@ import Vue from 'vue'
import debounce from 'lodash/debounce'
const defaultJS = `{
- data: {
- name: 'Zeus'
- },
+ data: {
+ name: 'Bootstrap-Vue',
+ show: true
+ },
+ watch: {
+ show(newVal, oldVal) {
+ console.log(
+ 'Alert is ' + (this.show ? 'visible' : 'hidden')
+ )
+ }
+ }
}`
-const defaultHTML = `
Hello {{ name }}! `
+
+const defaultHTML = `
+
+ {{ show ? 'Hide' : 'Show' }} Alert
+
+
+ Hello {{ name }}!
+
+
`
+
+// Maximum age of localstorage before we revert back to defaults
+// 7 days
+const maxRetention = 7 * 24 * 60 * 60 * 1000
+
+const removeNode = node => node && node.parentNode && node.parentNode.removeChild(node)
export default {
data () {
return {
html: '',
js: '',
- vm: null,
messages: [],
- originalLog: null,
- originalWarn: null,
- originalError: null,
+ logIdx: 0,
vertical: false,
- full: false,
- lazy_run_: null
+ full: false
}
},
head () {
@@ -198,120 +218,188 @@ export default {
'//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js'
]
},
+ isDefault () {
+ return this.js.trim() === defaultJS.trim() &&
+ this.html.trim() === defaultHTML.trim()
+ },
+ isOk () {
+ let o
+ const js = this.js.trim() || '{}'
+ try {
+ /* eslint-disable no-eval */
+ eval(`o = ${js}`)
+ /* eslint-enable no-eval */
+ } catch (err) {
+ return false
+ }
+ if (!this.html && !o.template && typeof o.render !== 'function') {
+ return false
+ }
+ return true
+ },
js_fiddle () {
- const js = `new Vue({el:'#app',\r\n${this.js.trim()}})`.trim()
- return `window.onload = function() {${js}}`
+ let js = this.js.trim() || '{}'
+ const comma = js === '{}' ? '' : ','
+ js = js.replace(/^\{/, `{\r\n el: '#app'${comma}\r\n`)
+ js = `new Vue(${js})`
+ return `window.onload = function() {\r\n${js}\r\n}`
},
html_fiddle () {
- return `
\r\n${this.html}\r\n
`.trim()
- },
- lazy_run () {
- if (!this.lazy_run_) {
- this.lazy_run_ = debounce(this.run.bind(this), 500)
- }
- return this.lazy_run_
+ return `
\r\n${this.html.trim()}\r\n
`
}
},
watch: {
html () {
- this.lazy_run()
+ this.run()
},
js () {
- this.lazy_run()
+ this.run()
}
},
- mounted () {
- this.load()
- this.run()
+ created () {
+ const self = this
+ // Non reactive property to store the playground vm
+ this.playVM = null
- if (typeof window !== 'undefined') {
- this.originalLog = console.log
- this.originalWarn = console.warn
- this.originalError = console.error
- const self = this
+ if (this.$isServer) {
+ this.run = () => {}
+ return
+ }
- console.warn = function () {
- self.log('warning', arguments)
- }
+ // Create our debounced runner
+ this.run = debounce(this._run, 500)
- console.log = function () {
- self.log('info', arguments)
- }
+ // Disable global error handler as it screws up out log capture
+ this.oldErrorHandler = Vue.config.errorHandler
+ Vue.config.errorHandler = null
- console.error = function () {
- self.log('danger', arguments)
+ // Override console.log
+ if (typeof window !== 'undefined' && console) {
+ const that = console
+ this.originalLog = console.log
+ console.log = function () {
+ self.log.call(self, 'info', ...arguments)
+ self.originalLog.apply(that, arguments)
}
}
},
+ mounted () {
+ // load our content into the editors after dom updated
+ this.$nextTick(this.load)
+ },
beforeDestroy () {
- if (typeof window !== 'undefined') {
+ if (typeof window !== 'undefined' && this.originalLog) {
console.log = this.originalLog
- console.warn = this.originalWarn
- console.error = this.originalError
}
- this.destroyVM()
+ if (this.oldErrorHandler) {
+ Vue.config.errorHandler = this.oldErrorHandler
+ }
+ if (!this.$isServer) {
+ this.destroyVM()
+ }
},
methods: {
- log (tag, args) {
+ log (tag, ...args) {
// We have to ignore props mutation warning due to vue bug when we have two instances
if (String(args[0]).indexOf('Avoid mutating a prop directly') !== -1) {
return
}
-
- const argsArr = [tag]
- for (let i = 0; i < args.length; i++) {
- argsArr.push(args[i])
+ const msg = args.map(String).join(' ')
+ if (this.messages.length && msg.indexOf('Error in render') !== -1 && msg === this.messages[0][1]) {
+ // prevent duplicate render errors
+ return
}
-
- this.originalLog.apply(console, argsArr)
-
if (this.messages.length > 10) {
this.messages.splice(10)
}
- this.messages.unshift([argsArr.shift(), argsArr.map(String).join(' ')])
+ this.messages.unshift([tag, msg, this.logIdx++])
},
destroyVM () {
- if (this.vm) {
+ if (this.playVM) {
+ let parent
try {
- this.vm.$destroy()
+ parent = this.playVM.$parent
+ this.playVM.$destroy()
+ removeNode(this.playVM.$el)
+ this.playVM.$el.innerHTML = ''
+ } catch (err) {
+ }
+ try {
+ parent.$destroy()
} catch (err) {
}
- this.vm = null
}
+ this.playVM = null
+ this.$refs.result.innerHTML = ''
},
- run () {
- // Commit latest changes
- this.commit()
+ createVM () {
+ let options
+ const js = this.js.trim() || '{}'
+ const html = this.html.trim()
+ const self = this
- // Destroy old VM if exists
- this.destroyVM()
+ const errHandler = (err, vm, info) => {
+ self.log('danger', `Error in ${info}: ${err.message}`)
+ self.destroyVM()
+ return false
+ }
- // Set HTML
- this.$refs.result.innerHTML = `
`
+ // Test JavaScript
+ try {
+ /* eslint-disable no-eval */
+ eval(`options = ${js}`)
+ /* eslint-enable no-eval */
+ } catch (err) {
+ errHandler(err, null, 'javascript')
+ this.playVM = null
+ return
+ }
- // Clear messages
- this.clear()
+ if (!html && !options.template && typeof options.render !== 'function') {
+ this.log('danger', 'No template or render function provided')
+ return
+ }
+
+ if (!options.render) {
+ options.template = `
${options.template || html}
`
+ } else {
+ delete options.template
+ }
+
+ let holder = document.createElement('div')
+ this.$refs.result.appendChild(holder)
- // Try Create new VM
try {
- let options
- try {
- /* eslint-disable no-eval */
- let js = this.js.trim()
- if (js.indexOf('{') !== 0) {
- js = `{${js}}`
- }
- eval(`options= ${js}`)
- } catch (err) {
- throw new Error(`Compiling JS: ${err}`)
- }
- options.router = this.$router
- options.el = '#result'
- options.template = `
${this.html}
`
- this.vm = new Vue(options)
+ const fakeParent = new Vue({
+ template: '
',
+ errorCaptured: errHandler
+ })
+ this.playVM = new Vue(Object.assign({}, options, {
+ el: holder,
+ // we set a fake parent so we can capture errors
+ parent: fakeParent,
+ // router needed for tooltips and popovers so they hide when route changes
+ router: this.$router
+ }))
} catch (err) {
- console.error(err)
+ holder = null
+ this.destroyVM()
+ errhandler(err, null, 'vm create')
+ return
+ }
+
+ this.save()
+ },
+ _run () {
+ if (this.$isServer) {
+ return
}
+ // Destroy old VM if exists
+ this.destroyVM()
+ // clear the log
+ this.clear()
+ // create and render the instance
+ this.createVM()
},
toggleVertical () {
this.vertical = !this.vertical
@@ -322,19 +410,43 @@ export default {
clear () {
this.messages.splice(0)
},
+ reset() {
+ // Needed to trick codemirror component to reload contents
+ this.js = this.html = ''
+ this.$nextTick(() => {
+ this.js = defaultJS.trim()
+ this.html = defaultHTML.trim()
+ this.save()
+ })
+ },
load () {
- if (typeof window === 'undefined' || !window.localStorage) {
+ const ls = window && window.localStorage
+ if (!ls) {
+ this.js = defaultJS.trim()
+ this.html = defaultHTML.trim()
return
}
- this.js = window.localStorage.getItem('playground_js') || defaultJS.trim()
- this.html = window.localStorage.getItem('playground_html') || defaultHTML.trim()
+ const ts = parseInt(ls.getItem('playground_ts'), 10) || 0
+ if (Date.now() - ts > maxRetention) {
+ // clear local storage if it is old
+ ls.removeItem('playground_js')
+ ls.removeItem('playground_html')
+ ls.removeItem('playground_ts')
+ }
+ this.js = ls.getItem('playground_js') || defaultJS.trim()
+ this.html = ls.getItem('playground_html') || defaultHTML.trim()
},
- commit () {
+ save () {
if (typeof window === 'undefined' || !window.localStorage) {
return
}
- window.localStorage.setItem('playground_js', this.js)
- window.localStorage.setItem('playground_html', this.html)
+ try {
+ window.localStorage.setItem('playground_js', this.js)
+ window.localStorage.setItem('playground_html', this.html)
+ window.localStorage.setItem('playground_ts', String(Date.now()))
+ } catch (err) {
+ // silently ignore errors on safari iOS private mode
+ }
}
}
}