Initiated ContextMenu

pull/132/head
cagataycivici 2019-12-04 15:14:21 +03:00
parent a7944e48ec
commit 9b67ebfaa2
8 changed files with 1013 additions and 1 deletions

View File

@ -128,6 +128,7 @@
<div> <div>
<router-link to="/menumodel">&#9679; MenuModel</router-link> <router-link to="/menumodel">&#9679; MenuModel</router-link>
<router-link to="/breadcrumb">&#9679; Breadcrumb</router-link> <router-link to="/breadcrumb">&#9679; Breadcrumb</router-link>
<router-link to="/contextmenu">&#9679; ContextMenu</router-link>
<router-link to="/menu">&#9679; Menu</router-link> <router-link to="/menu">&#9679; Menu</router-link>
<router-link to="/menubar">&#9679; Menubar</router-link> <router-link to="/menubar">&#9679; Menubar</router-link>
<router-link to="/tieredmenu">&#9679; TieredMenu</router-link> <router-link to="/tieredmenu">&#9679; TieredMenu</router-link>

View File

View File

@ -0,0 +1,260 @@
<template>
<transition name="p-contextmenu" @enter="onEnter" @leave="onLeave">
<div ref="container" class="p-contextmenu p-component" v-if="visible">
<ContextMenuSub :model="model" :root="true" @leaf-click="onLeafClick" />
</div>
</transition>
</template>
<script>
import DomHandler from '../utils/DomHandler';
import ContextMenuSub from './ContextMenuSub';
export default {
props: {
popup: {
type: Boolean,
default: false
},
model: {
type: Array,
default: null
},
appendTo: {
type: String,
default: null
},
autoZIndex: {
type: Boolean,
default: true
},
baseZIndex: {
type: Number,
default: 0
}
},
target: null,
outsideClickListener: null,
resizeListener: null,
pageX: null,
pageY: null,
data() {
return {
visible: false
};
},
beforeDestroy() {
this.restoreAppend();
this.unbindResizeListener();
this.unbindOutsideClickListener();
this.target = null;
},
methods: {
itemClick(event) {
const item = event.item;
if (item.command) {
item.command(event);
event.originalEvent.preventDefault();
}
this.hide();
},
toggle(event) {
if (this.visible)
this.hide();
else
this.show(event);
},
onLeafClick() {
this.hide();
},
show(event) {
this.pageX = event.pageX;
this.pageY = event.pageY;
if (this.visible) {
this.position();
}
else {
this.visible = true;
}
event.stopPropagation();
event.preventDefault();
},
hide() {
this.visible = false;
},
onEnter() {
this.appendContainer();
this.position();
this.bindOutsideClickListener();
this.bindResizeListener();
if (this.autoZIndex) {
this.$refs.container.style.zIndex = String(this.baseZIndex + DomHandler.generateZIndex());
}
},
onLeave() {
this.unbindOutsideClickListener();
this.unbindResizeListener();
},
position() {
let left = this.pageX + 1;
let top = this.pageY + 1;
let width = this.$refs.container.offsetParent ? this.$refs.container.offsetWidth : DomHandler.getHiddenElementOuterWidth(this.$refs.container);
let height = this.$refs.container.offsetParent ? this.$refs.container.offsetHeight : DomHandler.getHiddenElementOuterHeight(this.$refs.container);
let viewport = DomHandler.getViewport();
//flip
if (left + width - document.body.scrollLeft > viewport.width) {
left -= width;
}
//flip
if (top + height - document.body.scrollTop > viewport.height) {
top -= height;
}
//fit
if (left < document.body.scrollLeft) {
left = document.body.scrollLeft;
}
//fit
if (top < document.body.scrollTop) {
top = document.body.scrollTop;
}
this.$refs.container.style.left = left + 'px';
this.$refs.container.style.top = top + 'px';
},
bindOutsideClickListener() {
if (!this.outsideClickListener) {
this.outsideClickListener = (event) => {
if (this.visible && this.$refs.container && !this.$refs.container.contains(event.target)) {
this.hide();
}
};
document.addEventListener('click', this.outsideClickListener);
}
},
unbindOutsideClickListener() {
if (this.outsideClickListener) {
document.removeEventListener('click', this.outsideClickListener);
this.outsideClickListener = null;
}
},
bindResizeListener() {
if (!this.resizeListener) {
this.resizeListener = () => {
if (this.visible) {
this.hide();
}
};
window.addEventListener('resize', this.resizeListener);
}
},
unbindResizeListener() {
if (this.resizeListener) {
window.removeEventListener('resize', this.resizeListener);
this.resizeListener = null;
}
},
appendContainer() {
if (this.appendTo) {
if (this.appendTo === 'body')
document.body.appendChild(this.$refs.container);
else
document.getElementById(this.appendTo).appendChild(this.$refs.container);
}
},
restoreAppend() {
if (this.$refs.container && this.appendTo) {
if (this.appendTo === 'body')
document.body.removeChild(this.$refs.container);
else
document.getElementById(this.appendTo).removeChild(this.$refs.container);
}
}
},
computed: {
containerClass() {
return ['p-tieredmenu p-component', {
'p-tieredmenu-dynamic p-menu-overlay': this.popup
}];
}
},
components: {
'ContextMenuSub': ContextMenuSub
}
}
</script>
<style>
.p-contextmenu {
width: 12.5em;
padding: .25em;
position: absolute;
}
.p-contextmenu .p-menu-separator {
border-width: 1px 0 0 0;
}
.p-contextmenu ul {
list-style: none;
margin: 0;
padding: 0;
}
.p-contextmenu .p-submenu-list {
display: none;
position: absolute;
width: 12.5em;
padding: .25em;
z-index: 1;
}
.p-contextmenu .p-menuitem-active > .p-submenu-list {
display: block;
}
.p-contextmenu .p-menuitem-link {
padding: .25em;
display: block;
position: relative;
}
.p-contextmenu .p-menuitem-icon {
margin-right: .25em;
vertical-align: middle;
}
.p-contextmenu .p-menuitem-text {
vertical-align: middle;
}
.p-contextmenu .p-menuitem {
position: relative;
}
.p-contextmenu .p-menuitem-link .p-submenu-icon {
position: absolute;
margin-top: -.5em;
right: 0;
top: 50%;
}
.p-contextmenu .p-menuitem-active > .p-submenu > .p-submenu-list {
display: block !important;
}
.p-contextmenu-enter {
opacity: 0;
}
.p-contextmenu-enter-active {
-webkit-transition: opacity 250ms;
transition: opacity 250ms;
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<ul :class="containerClass" role="menu">
<template v-for="(item, i) of model">
<li role="menuitem" :class="getItemClass(item)" :style="item.style" v-if="item.visible !== false && !item.separator" :key="item.label + i"
@mouseenter="onItemMouseEnter($event, item)">
<router-link v-if="item.to" :to="item.to" class="p-menuitem-link"
@click.native="onItemClick($event, item)">
<span :class="['p-menuitem-icon', item.icon]"></span>
<span class="p-menuitem-text">{{item.label}}</span>
</router-link>
<a v-else :href="item.url||'#'" class="p-menuitem-link" :target="item.target"
@click="onItemClick($event, item)">
<span :class="['p-menuitem-icon', item.icon]"></span>
<span class="p-menuitem-text">{{item.label}}</span>
<span class="p-submenu-icon pi pi-fw pi-caret-right" v-if="item.items"></span>
</a>
<sub-menu :model="item.items" v-if="item.visible !== false && item.items" :key="item.label + '_sub_'"
@leaf-click="onLeafClick" :parentActive="item === activeItem" />
</li>
<li class="p-menu-separator" :style="item.style" v-if="item.visible !== false && item.separator" :key="'separator' + i"></li>
</template>
</ul>
</template>
<script>
import DomHandler from '../utils/DomHandler';
export default {
name: 'sub-menu',
props: {
model: {
type: Array,
default: null
},
root: {
type: Boolean,
default: false
},
parentActive: {
type: Boolean,
default: false
}
},
watch: {
parentActive(newValue) {
if (!newValue) {
this.activeItem = null;
}
}
},
data() {
return {
activeItem: null
}
},
methods: {
onItemMouseEnter(event, item) {
if (item.disabled) {
event.preventDefault();
return;
}
this.activeItem = item;
},
onItemClick(event, item) {
if (item.disabled) {
event.preventDefault();
return;
}
if (!item.url) {
event.preventDefault();
}
if (item.command) {
item.command({
originalEvent: event,
item: item
});
}
if (!item.items) {
this.onLeafClick();
}
},
onLeafClick() {
this.activeItem = null;
this.$emit('leaf-click');
},
position() {
const parentItem = this.$el.parentElement;
const containerOffset = DomHandler.getOffset(this.$el.parentElement)
const viewport = DomHandler.getViewport();
const sublistWidth = this.$el.offsetParent ? this.$el.offsetWidth : DomHandler.getHiddenElementOuterWidth(this.$el);
const itemOuterWidth = DomHandler.getOuterWidth(parentItem.children[0]);
this.$el.style.top = '0px';
if ((parseInt(containerOffset.left, 10) + itemOuterWidth + sublistWidth) > (viewport.width - DomHandler.calculateScrollbarWidth())) {
this.$el.style.left = -1 * sublistWidth + 'px';
}
else {
this.$el.style.left = itemOuterWidth + 'px';
}
},
getItemClass(item) {
return [
'p-menuitem', item.class, {
'p-menuitem-active': this.activeItem === item,
'p-disabled': item.disabled,
}
]
}
},
computed: {
containerClass() {
return {'p-submenu-list': !this.root};
}
}
}
</script>

View File

@ -14,6 +14,7 @@ import Checkbox from './components/checkbox/Checkbox';
import Chips from './components/chips/Chips'; import Chips from './components/chips/Chips';
import Column from './components/column/Column'; import Column from './components/column/Column';
import ColumnGroup from './components/columngroup/ColumnGroup'; import ColumnGroup from './components/columngroup/ColumnGroup';
import ContextMenu from './components/contextmenu/ContextMenu';
import DataTable from './components/datatable/DataTable'; import DataTable from './components/datatable/DataTable';
import DataView from './components/dataview/DataView'; import DataView from './components/dataview/DataView';
import DataViewLayoutOptions from './components/dataviewlayoutoptions/DataViewLayoutOptions'; import DataViewLayoutOptions from './components/dataviewlayoutoptions/DataViewLayoutOptions';
@ -89,6 +90,8 @@ Vue.component('Chart', Chart);
Vue.component('Checkbox', Checkbox); Vue.component('Checkbox', Checkbox);
Vue.component('Chips', Chips); Vue.component('Chips', Chips);
Vue.component('Column', Column); Vue.component('Column', Column);
Vue.component('ColumnGroup', ColumnGroup);
Vue.component('ContextMenu', ContextMenu);
Vue.component('DataTable', DataTable); Vue.component('DataTable', DataTable);
Vue.component('DataView', DataView); Vue.component('DataView', DataView);
Vue.component('DataViewLayoutOptions', DataViewLayoutOptions); Vue.component('DataViewLayoutOptions', DataViewLayoutOptions);
@ -99,7 +102,6 @@ Vue.component('Editor', Editor);
Vue.component('Fieldset', Fieldset); Vue.component('Fieldset', Fieldset);
Vue.component('FileUpload', FileUpload); Vue.component('FileUpload', FileUpload);
Vue.component('FullCalendar', FullCalendar); Vue.component('FullCalendar', FullCalendar);
Vue.component('ColumnGroup', ColumnGroup);
Vue.component('Inplace', Inplace); Vue.component('Inplace', Inplace);
Vue.component('InputSwitch', InputSwitch); Vue.component('InputSwitch', InputSwitch);
Vue.component('InputText', InputText); Vue.component('InputText', InputText);

View File

@ -116,6 +116,11 @@ export default new Router({
name: 'chips', name: 'chips',
component: () => import('./views/chips/ChipsDemo.vue') component: () => import('./views/chips/ChipsDemo.vue')
}, },
{
path: '/contextmenu',
name: 'contextmenu',
component: () => import('./views/contextmenu/ContextMenuDemo.vue')
},
{ {
path: '/datatable', path: '/datatable',
name: 'datatable', name: 'datatable',

View File

@ -0,0 +1,167 @@
<template>
<div>
<div class="content-section introduction">
<div class="feature-intro">
<h1>ContextMenu</h1>
<p>ContextMenu displays an overlay menu on right click of its target. Note that components like DataTable has special integration with ContextMenu. Refer to documentation of the individual documentation of the components having a special integration.</p>
</div>
</div>
<div class="content-section implementation">
<img alt="logo" src="demo/images/nature/nature3.jpg" @contextmenu="onImageRightClick">
<ContextMenu ref="menu" :model="items" />
</div>
<ContextMenuDoc />
</div>
</template>
<script>
import ContextMenuDoc from './ContextMenuDoc';
export default {
data() {
return {
items: [
{
label:'File',
icon:'pi pi-fw pi-file',
items:[
{
label:'New',
icon:'pi pi-fw pi-plus',
items:[
{
label:'Bookmark',
icon:'pi pi-fw pi-bookmark'
},
{
label:'Video',
icon:'pi pi-fw pi-video'
},
]
},
{
label:'Delete',
icon:'pi pi-fw pi-trash'
},
{
separator:true
},
{
label:'Export',
icon:'pi pi-fw pi-external-link'
}
]
},
{
label:'Edit',
icon:'pi pi-fw pi-pencil',
items:[
{
label:'Left',
icon:'pi pi-fw pi-align-left'
},
{
label:'Right',
icon:'pi pi-fw pi-align-right'
},
{
label:'Center',
icon:'pi pi-fw pi-align-center'
},
{
label:'Justify',
icon:'pi pi-fw pi-align-justify'
},
]
},
{
label:'Users',
icon:'pi pi-fw pi-user',
items:[
{
label:'New',
icon:'pi pi-fw pi-user-plus',
},
{
label:'Delete',
icon:'pi pi-fw pi-user-minus',
},
{
label:'Search',
icon:'pi pi-fw pi-users',
items:[
{
label:'Filter',
icon:'pi pi-fw pi-filter',
items:[
{
label:'Print',
icon:'pi pi-fw pi-print'
}
]
},
{
icon:'pi pi-fw pi-bars',
label:'List'
}
]
}
]
},
{
label:'Events',
icon:'pi pi-fw pi-calendar',
items:[
{
label:'Edit',
icon:'pi pi-fw pi-pencil',
items:[
{
label:'Save',
icon:'pi pi-fw pi-calendar-plus'
},
{
label:'Delete',
icon:'pi pi-fw pi-calendar-minus'
},
]
},
{
label:'Archieve',
icon:'pi pi-fw pi-calendar-times',
items:[
{
label:'Remove',
icon:'pi pi-fw pi-calendar-minus'
}
]
}
]
},
{
separator:true
},
{
label:'Quit',
icon:'pi pi-fw pi-power-off'
}
]
}
},
methods: {
onImageRightClick(event) {
this.$refs.menu.show(event);
}
},
components: {
'ContextMenuDoc': ContextMenuDoc
}
}
</script>

View File

@ -0,0 +1,456 @@
<template>
<div class="content-section documentation">
<TabView>
<TabPanel header="Documentation">
<h3>Import</h3>
<CodeHighlight lang="javascript">
import TieredMenu from 'primevue/tieredmenu';
</CodeHighlight>
<h3>MenuModel</h3>
<p>TieredMenu uses the common MenuModel API to define the items, visit <router-link to="/menumodel">MenuModel API</router-link> for details.</p>
<h3>Getting Started</h3>
<p>TieredMenu requires a collection of menuitems as its model.</p>
<CodeHighlight>
&lt;TieredMenu :model="items" /&gt;
</CodeHighlight>
<CodeHighlight lang="js">
export default {
data() {
return {
items: [
{
label:'File',
icon:'pi pi-fw pi-file',
items:[
{
label:'New',
icon:'pi pi-fw pi-plus',
items:[
{
label:'Bookmark',
icon:'pi pi-fw pi-bookmark'
},
{
label:'Video',
icon:'pi pi-fw pi-video'
},
]
},
{
label:'Delete',
icon:'pi pi-fw pi-trash'
},
{
separator:true
},
{
label:'Export',
icon:'pi pi-fw pi-external-link'
}
]
},
{
label:'Edit',
icon:'pi pi-fw pi-pencil',
items:[
{
label:'Left',
icon:'pi pi-fw pi-align-left'
},
{
label:'Right',
icon:'pi pi-fw pi-align-right'
},
{
label:'Center',
icon:'pi pi-fw pi-align-center'
},
{
label:'Justify',
icon:'pi pi-fw pi-align-justify'
},
]
},
{
label:'Users',
icon:'pi pi-fw pi-user',
items:[
{
label:'New',
icon:'pi pi-fw pi-user-plus',
},
{
label:'Delete',
icon:'pi pi-fw pi-user-minus',
},
{
label:'Search',
icon:'pi pi-fw pi-users',
items:[
{
label:'Filter',
icon:'pi pi-fw pi-filter',
items:[
{
label:'Print',
icon:'pi pi-fw pi-print'
}
]
},
{
icon:'pi pi-fw pi-bars',
label:'List'
}
]
}
]
},
{
label:'Events',
icon:'pi pi-fw pi-calendar',
items:[
{
label:'Edit',
icon:'pi pi-fw pi-pencil',
items:[
{
label:'Save',
icon:'pi pi-fw pi-calendar-plus'
},
{
label:'Delete',
icon:'pi pi-fw pi-calendar-minus'
},
]
},
{
label:'Archieve',
icon:'pi pi-fw pi-calendar-times',
items:[
{
label:'Remove',
icon:'pi pi-fw pi-calendar-minus'
}
]
}
]
},
{
separator:true
},
{
label:'Quit',
icon:'pi pi-fw pi-power-off'
}
]
}
}
}
</CodeHighlight>
<h3>Popup Mode</h3>
<p>TieredMenu is inline by default whereas popup mode is supported by enabling popup property and calling toggle method with an event of the target.</p>
<CodeHighlight>
&lt;Button type="button" label="Toggle" @click="toggle" /&gt;
&lt;TieredMenu ref="menu" :model="items" :popup="true" /&gt;
</CodeHighlight>
<CodeHighlight lang="js">
toggle(event) {
this.$refs.menu.toggle(event);
}
</CodeHighlight>
<h3>Properties</h3>
<p>Any attribute such as style and class are passed to the main container element. Following are the additional properties to configure the component.</p>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>model</td>
<td>array</td>
<td>null</td>
<td>An array of menuitems.</td>
</tr>
<tr>
<td>popup</td>
<td>boolean</td>
<td>false</td>
<td>Defines if menu would displayed as a popup.</td>
</tr>
<tr>
<td>appendTo</td>
<td>string</td>
<td>null</td>
<td>DOM element instance where the dialog should be mounted.</td>
</tr>
<tr>
<td>baseZIndex</td>
<td>number</td>
<td>0</td>
<td>Base zIndex value to use in layering.</td>
</tr>
<tr>
<td>autoZIndex</td>
<td>boolean</td>
<td>true</td>
<td>Whether to automatically manage layering.</td>
</tr>
</tbody>
</table>
</div>
<h3>Methods</h3>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Parameters</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>toggle</td>
<td>event: Browser event</td>
<td>Toggles the visiblity of the overlay.</td>
</tr>
<tr>
<td>show</td>
<td>event: Browser event <br />
target: Optional target if event.target would not be used</td>
<td>Shows the overlay.</td>
</tr>
<tr>
<td>hide</td>
<td>-</td>
<td>Hides the overlay.</td>
</tr>
</tbody>
</table>
</div>
<h3>Styling</h3>
<p>Following is the list of structural style classes, for theming classes visit <router-link to="/theming">theming</router-link> page.</p>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Element</th>
</tr>
</thead>
<tbody>
<tr>
<td>p-tieredmenu</td>
<td>Container element.</td>
</tr>
<tr>
<td>p-submenu-list</td>
<td>Submenu list element.</td>
</tr>
<tr>
<td>p-menuitem</td>
<td>Menuitem element.</td>
</tr>
<tr>
<td>p-menuitem-text</td>
<td>Label of a menuitem.</td>
</tr>
<tr>
<td>p-menuitem-icon</td>
<td>Icon of a menuitem.</td>
</tr>
<tr>
<td>p-submenu-icon</td>
<td>Arrow icon of a submenu.</td>
</tr>
</tbody>
</table>
</div>
<h3>Dependencies</h3>
<p>None.</p>
</TabPanel>
<TabPanel header="Source">
<a href="https://github.com/primefaces/primevue/tree/master/src/views/tieredmenu" class="btn-viewsource" target="_blank" rel="noopener noreferrer">
<span>View on GitHub</span>
</a>
<CodeHighlight>
<template v-pre>
&lt;h3&gt;Inline&lt;/h3&gt;
&lt;TieredMenu :model="items" /&gt;
&lt;h3&gt;Overlay&lt;/h3&gt;
&lt;Button type="button" label="Toggle" @click="toggle" /&gt;
&lt;TieredMenu ref="menu" :model="items" :popup="true" /&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
export default {
data() {
return {
items: [
{
label:'File',
icon:'pi pi-fw pi-file',
items:[
{
label:'New',
icon:'pi pi-fw pi-plus',
items:[
{
label:'Bookmark',
icon:'pi pi-fw pi-bookmark'
},
{
label:'Video',
icon:'pi pi-fw pi-video'
},
]
},
{
label:'Delete',
icon:'pi pi-fw pi-trash'
},
{
separator:true
},
{
label:'Export',
icon:'pi pi-fw pi-external-link'
}
]
},
{
label:'Edit',
icon:'pi pi-fw pi-pencil',
items:[
{
label:'Left',
icon:'pi pi-fw pi-align-left'
},
{
label:'Right',
icon:'pi pi-fw pi-align-right'
},
{
label:'Center',
icon:'pi pi-fw pi-align-center'
},
{
label:'Justify',
icon:'pi pi-fw pi-align-justify'
},
]
},
{
label:'Users',
icon:'pi pi-fw pi-user',
items:[
{
label:'New',
icon:'pi pi-fw pi-user-plus',
},
{
label:'Delete',
icon:'pi pi-fw pi-user-minus',
},
{
label:'Search',
icon:'pi pi-fw pi-users',
items:[
{
label:'Filter',
icon:'pi pi-fw pi-filter',
items:[
{
label:'Print',
icon:'pi pi-fw pi-print'
}
]
},
{
icon:'pi pi-fw pi-bars',
label:'List'
}
]
}
]
},
{
label:'Events',
icon:'pi pi-fw pi-calendar',
items:[
{
label:'Edit',
icon:'pi pi-fw pi-pencil',
items:[
{
label:'Save',
icon:'pi pi-fw pi-calendar-plus'
},
{
label:'Delete',
icon:'pi pi-fw pi-calendar-minus'
},
]
},
{
label:'Archieve',
icon:'pi pi-fw pi-calendar-times',
items:[
{
label:'Remove',
icon:'pi pi-fw pi-calendar-minus'
}
]
}
]
},
{
separator:true
},
{
label:'Quit',
icon:'pi pi-fw pi-power-off'
}
]
}
},
methods: {
toggle(event) {
this.$refs.menu.toggle(event);
}
}
}
</CodeHighlight>
</TabPanel>
</TabView>
</div>
</template>