Skip to content

Commit

Permalink
feat(ui): Ability to cancel navigation for QBreadcrumbsEl/QItem; enha…
Browse files Browse the repository at this point in the history
…nce cancel navigation options for QBtn/QRouteTab; refine QTab click event handler; improve router link mixin
  • Loading branch information
rstoenescu committed Oct 1, 2022
1 parent cebd845 commit 7d152e9
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 60 deletions.
13 changes: 5 additions & 8 deletions ui/src/components/breadcrumbs/QBreadcrumbsEl.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,16 @@ export default Vue.extend({
},

renderData () {
const acc = {
return {
staticClass: 'q-breadcrumbs__el q-link ' +
'flex inline items-center relative-position ' +
(this.disable !== true ? 'q-link--focusable' + this.linkClass : 'q-breadcrumbs__el--disabled'),
attrs: this.linkAttrs,
on: { ...this.qListeners }
on: {
...this.qListeners,
click: this.__navigateOnClick
}
}

if (this.hasRouterLink === true) {
acc.on.click = this.__navigateToRouterLink
}

return acc
}
},

Expand Down
56 changes: 56 additions & 0 deletions ui/src/components/breadcrumbs/QBreadcrumbsEl.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,61 @@
"default": {
"desc": "This is where custom content goes, unless 'icon' and 'label' props are not enough"
}
},

"events": {
"click": {
"desc": "Emitted when the component is clicked",
"params": {
"evt": {
"type": "Object",
"desc": "JS event object; If you are using route navigation ('to'/'replace'/'append' props) and you want to cancel navigation then call evt.preventDefault() synchronously in your event handler",
"required": true,
"__exemption": [ "examples" ]
},
"navigateFn": {
"type": "Function",
"desc": "Available ONLY if you are using route navigation ('to'/'replace'/'append' props); When you need to control the time at which the component should trigger the route navigation then call evt.preventDefault() synchronously and then call this function at your convenience; Useful if you have async work to be done before the actual route navigation or if you want to redirect somewhere else",
"addedIn": "v1.21.0",
"params": {
"opts": {
"type": "Object",
"desc": "Optional options",
"definition": {
"to": {
"type": [ "String", "Object" ],
"desc": "Equivalent to Vue Router <router-link> 'to' property; Specify it explicitly otherwise it will be set with same value as component's 'to' prop",
"examples": [
"/home/dashboard",
"{ name: 'my-route-name' }"
]
},

"replace": {
"type": "Boolean",
"desc": "Equivalent to Vue Router <router-link> 'replace' property; Specify it explicitly otherwise it will be set with same value as component's 'replace' prop"
},

"append": {
"type": "Boolean",
"desc": "Equivalent to Vue Router <router-link> 'append' property",
"default": "Tab's 'append' property"
},

"returnRouterError": {
"type": "Boolean",
"desc": "Return the router error, if any; Otherwise the returned Promise will always fulfill"
}
}
}
},
"returns": {
"type": "Promise<any>",
"desc": "Returns the router's navigation promise",
"__exemption": [ "examples" ]
}
}
}
}
}
}
12 changes: 1 addition & 11 deletions ui/src/components/btn/QBtn.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,7 @@ export default Vue.extend({
}
}

if (this.hasRouterLink === true) {
const go = returnError => this.__navigateToRouterLink(e, true, returnError)

stopAndPrevent(e)

this.$emit('click', e, go)
e.navigate !== false && go()
}
else {
this.$emit('click', e)
}
this.__navigateOnClick(e)
},

__onKeydown (e) {
Expand Down
43 changes: 35 additions & 8 deletions ui/src/components/btn/QBtn.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,28 +55,55 @@

"events": {
"click": {
"desc": "Emitted when component is clicked (activated)",
"desc": "Emitted when the component is clicked",
"params": {
"evt": {
"type": "Object",
"desc": "JS event object; If you want to cancel navigation set synchronously 'evt.navigate' to false",
"desc": "JS event object; If you are using route navigation ('to'/'replace'/'append' props) and you want to cancel navigation then call evt.preventDefault() synchronously in your event handler",
"required": true,
"__exemption": [ "examples" ]
},
"navigateFn": {
"type": "Function",
"desc": "When you need to control the time at which the button should trigger the route navigation then set 'evt.navigate' to false and call this function; Useful if you have async work to be done before the actual route navigation",
"desc": "Available ONLY if you are using route navigation ('to'/'replace'/'append' props); When you need to control the time at which the component should trigger the route navigation then call evt.preventDefault() synchronously and then call this function at your convenience; Useful if you have async work to be done before the actual route navigation or if you want to redirect somewhere else",
"params": {
"returnError": {
"type": "Boolean",
"desc": "Return the router error, if any; Otherwise the returned Promise will always fulfill",
"addedIn": "v1.21"
"opts": {
"type": "Object",
"desc": "Optional options",
"addedIn": "v1.21.0",
"definition": {
"to": {
"type": [ "String", "Object" ],
"desc": "Equivalent to Vue Router <router-link> 'to' property; Specify it explicitly otherwise it will be set with same value as component's 'to' prop",
"examples": [
"/home/dashboard",
"{ name: 'my-route-name' }"
]
},

"replace": {
"type": "Boolean",
"desc": "Equivalent to Vue Router <router-link> 'replace' property; Specify it explicitly otherwise it will be set with same value as component's 'replace' prop"
},

"append": {
"type": "Boolean",
"desc": "Equivalent to Vue Router <router-link> 'append' property",
"default": "Tab's 'append' property"
},

"returnRouterError": {
"type": "Boolean",
"desc": "Return the router error, if any; Otherwise the returned Promise will always fulfill"
}
}
}
},
"returns": {
"type": "Promise<any>",
"desc": "Returns the router's navigation promise",
"__exemption": [ "examples" ],
"addedIn": "v1.21"
"addedIn": "v1.21.0"
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions ui/src/components/item/QItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ export default Vue.extend({
}
}

this.hasRouterLink === true && this.__navigateToRouterLink(e)
this.$emit('click', e)
this.__navigateOnClick(e)
}
},

Expand Down
56 changes: 56 additions & 0 deletions ui/src/components/item/QItem.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,61 @@
"default": {
"desc": "This is where QItem's content goes"
}
},

"events": {
"click": {
"desc": "Emitted when the component is clicked",
"params": {
"evt": {
"type": "Object",
"desc": "JS event object; If you are using route navigation ('to'/'replace'/'append' props) and you want to cancel navigation then call evt.preventDefault() synchronously in your event handler",
"required": true,
"__exemption": [ "examples" ]
},
"navigateFn": {
"type": "Function",
"desc": "Available ONLY if you are using route navigation ('to'/'replace'/'append' props); When you need to control the time at which the component should trigger the route navigation then call evt.preventDefault() synchronously and then call this function at your convenience; Useful if you have async work to be done before the actual route navigation or if you want to redirect somewhere else",
"addedIn": "v1.21.0",
"params": {
"opts": {
"type": "Object",
"desc": "Optional options",
"definition": {
"to": {
"type": [ "String", "Object" ],
"desc": "Equivalent to Vue Router <router-link> 'to' property; Specify it explicitly otherwise it will be set with same value as component's 'to' prop",
"examples": [
"/home/dashboard",
"{ name: 'my-route-name' }"
]
},

"replace": {
"type": "Boolean",
"desc": "Equivalent to Vue Router <router-link> 'replace' property; Specify it explicitly otherwise it will be set with same value as component's 'replace' prop"
},

"append": {
"type": "Boolean",
"desc": "Equivalent to Vue Router <router-link> 'append' property",
"default": "Tab's 'append' property"
},

"returnRouterError": {
"type": "Boolean",
"desc": "Return the router error, if any; Otherwise the returned Promise will always fulfill"
}
}
}
},
"returns": {
"type": "Promise<any>",
"desc": "Returns the router's navigation promise",
"__exemption": [ "examples" ]
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion ui/src/components/tabs/QRouteTab.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"navigateFn": {
"type": "Function",
"desc": "When you need to control the time at which the tab should trigger the route navigation then set 'evt.navigate' to false and call this function; Useful if you have async work to be done before the actual route navigation",
"desc": "When you need to control the time at which the tab should trigger the route navigation then set 'evt.navigate' to false and call this function at your convenience; Useful if you have async work to be done before the actual route navigation or you want to navigate elsewhere",
"params": {
"to": {
"type": [ "String", "Object" ],
Expand Down
49 changes: 30 additions & 19 deletions ui/src/components/tabs/QTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { stop, stopAndPrevent } from '../../utils/event.js'
import { mergeSlot } from '../../utils/private/slot.js'
import { isKeyCode, shouldIgnoreKey } from '../../utils/private/key-composition.js'
import uid from '../../utils/uid.js'
import { isDeepEqual } from '../../utils/is.js'

let id = 0

Expand Down Expand Up @@ -138,39 +139,49 @@ export default Vue.extend({
}

if (this.hasRouterLink === true) {
const go = (to = this.to, append = this.append, replace = this.replace) => {
const sameInternalRoute = to === this.to && append === this.append

const resolvedLink = sameInternalRoute === true
? this.resolvedLink
: this.__getLink(to, append)

if (resolvedLink === null) {
return Promise.resolve()
}
const go = (first, second, third) => {
// for backwards compatibility
const { to, replace, append, returnRouterError } = e.navigate === false
? { to: first, replace: second, append: third }
: (first || {})

// if requiring to go to another route, then we
// let the QTabs route watcher do its job,
// otherwise directly select this
let failed
const reqId = sameInternalRoute === true
let hardError
const reqId = to === void 0 || (append === this.append && isDeepEqual(to, this.to) === true)
? (this.$tabs.avoidRouteWatcher = uid())
: null

return this.$router[replace === true ? 'replace' : 'push'](resolvedLink.location)
.catch(() => { failed = true })
.finally(() => {
return this.__navigateToRouterLink(e, { to, replace, append, returnRouterError: true })
.catch(err => { hardError = err })
.then(result => {
if (reqId === this.$tabs.avoidRouteWatcher) {
this.$tabs.avoidRouteWatcher = false
failed !== true && this.$tabs.__updateModel({ name: this.name })

// if we don't have any hard errors, except for
// when navigating to the same route (on all other errors,
// like when navigation was aborted in a nav guard, we don't activate this tab)
if (
hardError === void 0 ||
hardError.message.startsWith('Avoided redundant navigation') === true
) {
this.$tabs.__updateModel({ name: this.name })
}
}

return hardError !== void 0 && returnRouterError === true
? Promise.reject(hardError)
: result
})
}

stopAndPrevent(e)

this.qListeners.click !== void 0 && this.$emit('click', e, go)
e.navigate !== false && go()

// for backwards compatibility
e.navigate === false && e.preventDefault()

e.defaultPrevented !== true && go()

return
}
Expand Down
Loading

0 comments on commit 7d152e9

Please sign in to comment.