I have an vue app that user vuetify. The app is used inside existing cms.
When a dropdown select is opened a click outside the app does not close the dropdown. If the click is inside the app region, the dropdown closes.
Any idea how can I trigger close for the opened dropdown on click outside the app ?
new Vue({
el: '#cartbutton',
data() {
return {
items: [{text:'a'}, {text:'b'}]
}
}
})
.existing-cms {
padding: 40px 50px;
}
#app {
background-color: rgba(0,0,0,0.2);
padding: 10px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vuetify/dist/vuetify.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel=stylesheet>
<div class="existing-cms">
<p>Click on the select dropdown and leave it open.</p>
<p>
Click on this white region outside the app should somehow close
opened select in the app.
</p>
<div id="cartbutton">
<v-app>
<span>Clicking here insde the app closed the opened select</span>
<br><br>
<v-select label="Click me and leave it opened. Then click in the white region." :items="items"></v-select>
</v-app>
</div>
</div>
Here is a codepen link https://codepen.io/darkopetreski/pen/OGMvop
EDIT:
It seems that this is reported as a bug here https://github.com/vuetifyjs/vuetify/issues/3144 and the suggestion is to use data-app="true" at the root element, but this approach is not good, since it will mess up stuff (at least was not working well for me).
This is a rather hacky solution but it seems to work. There are two changes:
1) Add a window click listener that calls the blur method on the v-select. This hides the control. To facilitate this I added a ref to the v-select component.
2) To prevent this firing when they click inside the app or on the v-select, I added a stop propagation #click.stop onto the container.
https://codepen.io/anon/pen/BeoOMz
new Vue({
el: '#cartbutton',
data() {
return {
items: [{text:'a'}, {text:'b'}]
}
},
mounted() {
window.addEventListener("click",() => {
this.$refs.select.blur();
});
}
})
.existing-cms {
padding: 40px 50px;
}
#app {
background-color: rgba(0,0,0,0.2);
padding: 10px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vuetify/dist/vuetify.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel=stylesheet>
<div class="existing-cms">
<p>Click on the select dropdown and leave it open.</p>
<p>
Click on this white region outside the app should somehow close
opened select in the app.
</p>
<div id="cartbutton" #click.stop>
<v-app>
<span>Clicking here insde the app closed the opened select</span>
<br><br>
<v-select ref="select" label="Click me and leave it opened. Then click in the white region." :items="items"></v-select>
</v-app>
</div>
</div>
Related
I have created a webcomponent for a generic input boxes that i needed across multiple projects.
the design functionality remains same only i have to use switch themes on each projects.so i have decided to go on with webcomponents.One of the projects is based on Vue Js.In Vue js the DOM content is re-rendered while each update for enabling reactivity. That re-rendering of vue template is reinitializing my custom webcomponent which will result in loosing all my configurations i have assigned to the component using setters.
I know the below solutions. but i wanted to use a setter method.
pass data as Attributes
Event based passing of configurations.
Using Vue-directives.
using v-show instead of v-if
-- Above three solutions doesn't really match with what i am trying to create.
I have created a sample project in jsfiddle to display my issue.
Each time i an unchecking and checking the checkbox new instances of my component is creating. which causes loosing the theme i have selected. (please check he active boxes count)
For this particular example i want blue theme to be displayed. but it keep changing to red
JSFiddle direct Link
class InputBox extends HTMLElement {
constructor() {
super();
window.activeBoxes ? window.activeBoxes++ : window.activeBoxes = 1;
var shadow = this.attachShadow({
mode: 'open'
});
var template = `
<style>
.blue#iElem {
background: #00f !important;
color: #fff !important;
}
.green#iElem {
background: #0f0 !important;
color: #f00 !important;
}
#iElem {
background: #f00;
padding: 13px;
border-radius: 10px;
color: yellow;
border: 0;
outline: 0;
box-shadow: 0px 0px 14px -3px #000;
}
</style>
<input id="iElem" autocomplete="off" autocorrect="off" spellcheck="false" type="text" />
`;
shadow.innerHTML = template;
this._theme = 'red';
this.changeTheme = function(){
this.shadowRoot.querySelector('#iElem').className = '';
this.shadowRoot.querySelector('#iElem').classList.add(this._theme);
}
}
connectedCallback() {
this.changeTheme();
}
set theme(val){
this._theme = val;
this.changeTheme();
}
}
window.customElements.define('search-bar', InputBox);
<!DOCTYPE html>
<html>
<head>
<title>Wrapper Component</title>
<script src="https://unpkg.com/vue"></script>
<style>
html,
body {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
search-bar {
top: 100px;
position: absolute;
left: 300px;
}
input {
min-width: 20px;
padding: 25px;
top: 100px;
position: absolute;
}
</style>
</head>
<body>
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<div class='parent' contentEditable='true' v-if='visible'>
<search-bar ref='iBox'></search-bar>
</div>
<input type='checkbox' v-model='visible'>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select>
<slot></slot>
</select>
</script>
<script>
var vm = new Vue({
el: "#el",
template: "#demo-template",
data: {
visible: true,
},
mounted(){
let self = this
setTimeout(()=>{
self.$refs.iBox.theme = 'blue';
} , 0)
}
});
</script>
</body>
</html>
<div class='parent' contentEditable='true' v-if='visible'>
<search-bar ref='iBox'></search-bar>
</div>
<input type='checkbox' v-model='visible'>
Vue's v-if will add/remove the whole DIV from the DOM
So <search-bar> is also added/removed on every checkbox click
If you want a state for <search-bar> you have to save it someplace outside the <search-bar> component:
JavaScript variable
localStorage
.getRootnode().host
CSS Properties I would go with this one, as they trickle into shadowDOM
...
...
Or change your checkbox code to not use v-if but hide the <div> with any CSS:
display: none
visibility: hidden
opacity: 0
move to off screen location
height: 0
...
and/or...
Managing multiple screen elements with Stylesheets
You can easily toggle styling using <style> elements:
<style id="SearchBox" onload="this.disabled=true">
... lots of CSS
... even more CSS
... and more CSS
</style>
The onload event makes sure the <style> is not applied on page load.
activate all CSS styles:
(this.shadowRoot || document).getElementById("SearchBox").disabled = false
remove all CSS styles:
(this.shadowRoot || document).getElementById("SearchBox").disabled = true
You do need CSS Properties for this to work in combo with shadowDOM Elements.
I prefer native over Frameworks. <style v-if='visible'/> will work.. by brutally removing/adding the stylesheet.
I have a web page that can display several html style dialogs. I'd like to have code in just one place that can sense that the enter key was pressed and call the same method as would be called by clicking the OK button for the dialog. This allows visitor to press enter in the dialog to close it instead of having to click the OK button. Pretty simple stuff.
I'd to use jQuery to create a global keydown handler that looks for the enter key and calls the vuejs dlogClose method. I have created a minimal version of this below. In real life there would be multiple dialogs and more complex code to find the appropriate dlogClose method for the particular dialog. But I have removed that logic to create a minimal code example of where Vue is not behaving as expected.
Click the button to open the dialog. Then click the ok button. It will display an alert saying "dlogClose called" and then it will close the dialog. Works exactly as expected.
Then click the button to open the dialog again. This time press the enter key. The jQuery global event handler will see the enter key and call the same dlogClose method. That method will display an alert saying "dlogClose called" as expected BUT it's won't close the dialog! What? That's totally unexpected behaviour.
Can you explain why this behavior is valid? Or is this some sort of vue library bug?
var app = new Vue({
el: '#app',
data: {
dlogVisible: false
},
methods: {
dlogClose: function () {
alert("dlogClose called");
app.dlogVisible = false;
}
},
created: function () {
$(document).keypress(function (e) {
if (e.which == 13) {
app.dlogClose();
}
});
}
});
.dlog{
height:200px; width:200px; border: solid 1px gray;
padding: 20px; box-shadow: 0px 3px 15px gray;
position: absolute; top:40px; left:40px; background-color:white
}
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://unpkg.com/vue#2.2.5/dist/vue.min.js"></script>
<div>Global Enter Key Example</div>
<br />
<div id="app">
<div class="dlog" v-if="dlogVisible">
Pressing enter calls same method as clicking OK button<br />
<br />
<button type="button" v-on:click="dlogClose()">OK</button>
</div>
<button type="button" v-on:click="dlogVisible=true;">Open Dialog</button>
</div>
The issue here is that pressing the enter key while the dialog is open also triggers the open dialog button. So in essence, the dialog is closed and the immediately re-opened. Use preventDefault to stop that from happening.
$(document).keypress(function (e) {
e.preventDefault()
if (e.which == 13) {
app.dlogClose();
}
});
Here is the code updated.
var app = new Vue({
el: '#app',
data: {
dlogVisible: false
},
methods: {
dlogClose: function () {
alert("dlogClose called");
app.dlogVisible = false;
}
},
created: function () {
$(document).keypress(function (e) {
e.preventDefault()
if (e.which == 13) {
app.dlogClose();
}
});
}
});
.dlog{
height:200px; width:200px; border: solid 1px gray;
padding: 20px; box-shadow: 0px 3px 15px gray;
position: absolute; top:40px; left:40px; background-color:white
}
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://unpkg.com/vue#2.2.5/dist/vue.min.js"></script>
<div>Global Enter Key Example</div>
<br />
<div id="app">
<div class="dlog" v-if="dlogVisible">
Pressing enter calls same method as clicking OK button<br />
<br />
<button type="button" v-on:click="dlogClose()">OK</button>
</div>
<button type="button" v-on:click="dlogVisible=true;">Open Dialog</button>
</div>
You could create a jQuery event listener in the created () method within the top most page component (the App component). In that top level app component, make a method to to fire each time the enter button is hit of whenever it meets the condition you specify. For simplicity, you can then increment a vuex state counter or something. Whatever child components you have can watch for the counter to change and react accordingly (again based on conditions you set). I am not very knowledgeable about dispatching but that might be another route to investigate.
I have a pretty basic modal function on my website which displays a like, repost & comment modal, above the track, in which trigger has been clicked.
However, I have had to set it up in an odd way due to the tracks being displayed procedurally through PHP.
This is my simplified markup:
<div class="f-wave-send">
<div class="t__trigger-actions"> <!-- this is the modal trigger button !-->
<span id="trigger-actions-modal"></span>
</div>
<div class="f-track__actions" id="track-actions"> <!-- this is the modal !-->
<div class="close-actions"> <!-- this is the close button !-->
<span></span>
</div>
<div class="f-track-actions-inner">
<!-- modal contents !-->
</div>
</div>
</div>
This markup is duplicated across the page (to represent each track within the database); similar to Facebook's feed.
This is the JS which controls all the modals functionalities:
$(".t__trigger-actions").click(function(){ // when one of the modal triggers is clicked
var parent = $(this).parent();
parent.find(".f-track__actions").css("display", "block"); // get the modal within ONLY the same container to display
parent.addClass("modal__open"); // and add the class modal__open to the container
});
$(".close-actions").click(function(){ // when the close button is clicked
$(".modal__open").children(".f-track__actions").css("display", "none"); // hide the modal
$(".f-wave-send").removeClass("modal__open"); // remove the modal__open class from the container
});
$(document).bind('click', function(e) { // if user clicks on anything but the main container
if(!$(e.target).is('.modal__open')) {
$(".modal__open").children(".f-track__actions").css("display", "none"); // hide the modal
$(".f-wave-send").removeClass("modal__open"); // remove the modal__open class from the container
}
});
I have commented where possible trying to explain what is going on. But I'll explain here once again;
When a user clicks on one of the many modal triggers within the document, it will get that triggers modal, and display it (and add the class modal__open to it's parent container).
If a user clicks on the close button (or on the document), close that same modal.
I've been stuck trying to figure this out for a little while now, so all help (and suggestions) are appreciated.
Thanks.
EDIT:
What I want to happen is, when the modal opens, it ONLY closes when the user clicks out of the modal, OR on the close button (if that makes sense).
Is this what you want?
- Added closest() instead of parent just in case its not a direct parent.
- Added e.stopPropagation() to the 'open' button.
$(document).ready(function() {
$(".t__trigger-actions").click(function(e) {
var topClass = $(this).closest('.f-wave-send');
topClass.find(".f-track__actions").css("display", "block");
topClass.addClass("modal__open");
$(this).next().addClass("modal__open");
e.stopPropagation();
});
$(".close-actions").click(function() {
$(".modal__open").children(".f-track__actions").css("display", "none");
$(".f-wave-send").removeClass("modal__open");
});
$(document).bind('click', function(e) {
var container = $(".modal__open");
if (!container.is(e.target) && container.has(e.target).length === 0) {
$(".modal__open").children(".f-track__actions").css("display", "none");
$(".f-wave-send").removeClass("modal__open");
$(".f-track__actions").removeClass("modal__open");
}
});
})
.f-track__actions {
display: none;
}
.f-wave-send {
border: 2px solid red;
}
.t__trigger-actions {
height: 40px;
border: 1px solid green;
}
.f-track__actions {
height: 60px;
border: 1px solid blue;
}
.close-actions {
display: inline-block;
width: 50px;
height: 30px;
background-color: #ddd;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="f-wave-send">
<div class="t__trigger-actions">
<!-- this is the modal trigger button !-->
<span id="trigger-actions-modal">Open</span>
</div>
<div class="f-track__actions" id="track-actions">
<!-- this is the modal !-->
<div class="close-actions">
<!-- this is the close button !-->
<span>Close</span>
</div>
<div class="f-track-actions-inner">
<input/>
<!-- modal contents !-->
</div>
</div>
</div>
I'm new on WinJS development and I started with the WinJS tutorial. After updating my HTML and adding a WinJS.UI.SplitView I can't see the WinJS.UI.SplitViewCommands.
BTW when I toggle the pane I can't see the icons neither
Result
This is my code.
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WePin</title>
<!-- WinJS references -->
<link href="WinJS/css/ui-dark.css" rel="stylesheet" />
<script src="WinJS/js/base.js"></script>
<script src="WinJS/js/ui.js"></script>
<!-- WePin references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body class="win-type-body">
<div id="app" class="show-home">
<div class="splitView" data-win-control="WinJS.UI.SplitView">
<!-- Pane area -->
<div>
<div class="header">
<!--this is a button that allows you to "toggle" the menu in and out of view -->
<button class="win-splitviewpanetoggle" data-win-control="WinJS.UI.SplitViewPaneToggle"
data-win-options="{ splitView: select('.splitView') }"></button>
<div class="title">SplitView Pane area</div>
</div>
<!--this is where the navigation icons go -->
<div class="nav-commands">
<div data-win-control="WinJS.UI.SplitViewCommand" data-win-options="{
label: 'Alki Trail',
icon: 'mappin',
onclick: mySplitView.trailClicked
}"></div>
</div>
</div>
<!--/ Pane area-->
<!-- Content area -->
<div class="contenttext"><h2 class="win-h2">SplitView Content area</h2></div>
<!--/ Content area -->
</div>
</div>
</body>
</html>
CSS
.content,
#app {
height: 100%;
}
#app .win-splitviewpanetoggle {
float: left;
}
/* SplitView pane content style*/
#app .header {
white-space: nowrap;
}
#app .title {
font-size: 25px;
left: 50px;
margin-top: 6px;
vertical-align: middle;
display: inline-block;
}
#app .nav-commands {
margin-top: 20px;
}
#app .win-splitview-pane-closed .win-splitviewcommand-label {
/* Make sure pane content doesn't scroll if
SplitViewCommand label receives focus while pane is closed.
*/
visibility: hidden;
}
/*SplitView content style*/
#app .win-splitview-content {
background-color: rgb(112,112,112);
}
#app .contenttext {
margin-left: 20px;
margin-top: 6px;
}
Javascript
(function () {
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize your application here.
} else {
// TODO: This application was suspended and then terminated.
// To create a smooth user experience, restore application state here so that it looks like the app never stopped running.
}
args.setPromise(WinJS.UI.processAll());
}
};
app.oncheckpoint = function (args) {
// TODO: This application is about to be suspended. Save any state that needs to persist across suspensions here.
// You might use the WinJS.Application.sessionState object, which is automatically saved and restored across suspension.
// If you need to complete an asynchronous operation before your application is suspended, call args.setPromise().
};
app.start();
})();
I had the same problem. Fixed by updating WinJS to latest version. Based on the changelog, SplitViewCommand is a new feature on WinJS v4.2
This is on my plugin page on Git and I have two interactive demo in the web page. In one of the demo page, I have a small dialog that opens when you click on a div.
The weird issue is that this dialog is getting opened when I click on the top title that says attrchange beta . This happens only if the first click is on the title attrchange beta, clicking any other element in page fixes this issue.
The plugin page http://meetselva.github.io/attrchange/ [Fixed, use the below URL to see the problem]
http://meetselva.github.io/attrchange/index_so_issue.html
Below is the code,
<!-- The title -->
<h1 id="project_title">attrchange <span class="beta" style="text-decoration: line-through;" title="Almost there...">beta</span></h1>
<!-- Main dialog that has link to the sub-dialog -->
<div id="attributeChanger">
<h4 class="title">Attribute Changer</h4>
<p>Listed below are the attributes of the div:</p>
<div class="attrList"></div>
<div class="addAttribute text-right">add new attribute</div>
</div>
<!-- Sub-dialog -->
<div id="addOrmodifyAttr" title="Add/Modify Attribute">
<h4 class="title">Add/Modify Attribute</h4>
<p><b>Attr Name</b> <input type="text" class="float-right attrName"></p>
<p><b>Attr Value</b> <input type="text" class="float-right attrValue"/></p>
<div class="clear"> </div>
<button type="button" class="float-right close">Close</button>
<button type="button" class="float-right update">Update</button>
<div class="clear"></div>
</div>
JS:
var $attributeChanger = $('#attributeChanger');
var $attrName = $('.attrName', '#addOrmodifyAttr'),
$attrValue = $('.attrValue', '#addOrmodifyAttr'),
$attrAMUpdate = $('.update', '#addOrmodifyAttr');
//Handler to open the sub-dialog
$attributeChanger.on('click', '.addAttribute', function () {
$attrName.val('').removeClass('nbnbg');
$attrValue.val('');
$('#addOrmodifyAttr, #overlay').show();
});
The problem is the CSS applied to your #attributeChanger div.
If you look at the CSS applied to it:
#attributeChanger {
background-color: #FEFFFF;
border: 1px solid #4169E1;
color: #574353;
font-size: 0.9em;
margin: 10px;
min-height: 50px;
min-width: 150px;
opacity: 0;
padding: 2px;
position: absolute;
top: -200px;
z-index: 1;
}
You'll notice that the position is absolute, and it's positioned over your logo. So what you're clicking is actually your #attributeChanger div.
To fix it, you can hide #attributeChanger using display: none;, then use $('#attributeChanger').show(); in jQuery when it comes into actual view.
The pop up is showing because this code is running:
}).on('click', '.addAttribute', function () {
$attrName.val('').removeClass('nbnbg');
$attrValue.val('');
$('#addOrmodifyAttr, #overlay').show();
This is because the DIV with the class addAttribute is over the title DIV.
You can either move the 'addAttribute' DIV, or remove the last line of that onclick function.
That is because you element is hover your title and detect the click on himself and open(i don't know why it open, i didnt examine your entire code). But when you click anywhere else, your code is changing his position so it is not over the title.
The easiest fix is to change you #attributeChanger CSS top to -100px (that's the value when you click on the document) OR add a display : none.
EDIT : Axel answer show what I mean by "element is hover your title".