Dojo 1.8
If I have a widget that has an overridden onclick setter:
// in mypackage/ClickableWidget
_setOnClickAttr: function( onClick ) {
this.onClick = onClick;
// handle the event...
}
...then it get's invoked just fine when I create the widget programattically:
new ClickableWidget({
onClick: function() {
alert('clicked');
}
});
BUT... it seems when I create my widget declarativly:
<div data-dojo-type="mypackage/ClickableWidget">
<script type="dojo/on" data-dojo-event="click">
alert('clicked');
</script>
</div>
...it doesn't trigger my overridden onclick setter.
Is this just a fact of dojo, or am I doing something wrong?
also, is there a better term to refer to what I am calling my "overridden setter"?
Update 2
Maybe the way you write the script tag is wrong:
Find this :
<script type="dojo/method" data-dojo-event="onClick" data-dojo-args="evt">
Here's the link to the dojo-source: http://dojotoolkit.org/reference-guide/1.7/dojo/isFunction.html
My last guess. hope this helps.
Related
To do something after the DOM has been updated by Vue, you use the $nextTick binding.
To do something after a css transition has completed, you can use transitionend event.
I have a dynamic list in which things are added and removed by user actions. When removed, there is a CSS animation and then I need to check the state of the DOM immediately after the element is gone.
I was thinking that the $nextTick after the transitionend would be the state of the DOM immediately after the list item is removed, but it is not.
I need to do something after the transition has ended and the element from a list has been removed from the DOM.
Right now I have:
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>
dismissMessage(message){
const vm = this;
this.$refs[message.id][0].addEventListener("transitionend", function(){
vm.$nextTick(function(){
//This is called, but appears to be called before the element is actually removed from the DOM
//I need to query the DOM immediately after this element is removed
});
});
this.messages.splice(this.messages.indexOf(message), 1);
}
In the mounted function, I have added a MutationObserver that appears to be working as needed. I'll put this here as an answer as it does technically work and may be helpful to others, but I'm still interested in a better answer if Vue has something built in for this.
mounted(){
const vm = this;
const listItemRemoved = new MutationObserver(function(e){
if (e[0].removedNodes.length){
console.log("Removed");
}
});
listItemRemoved.observe(this.$el, {childList: true});
}
Perhaps you could use a custom directive. Perform the actions you need inside the unbind hook ..
created() {
this.vm = this
},
directives: {
foo: {
unbind(el, binding) {
// Here you can perform the actions you need.
// You can access the Vue instance using binding.value (eg: binding.value.$el)
}
}
},
And in your template ..
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id" v-foo="vm">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>
I need to submit a form programmatically, but I need it to preventDefault as well.
Right now I have the following:
submit() {
this.$refs.form.submit()
}
It is working fine, but I cannot prevent default on the submit which in the end, refreshes the page.
Short answer
You can add the .prevent modifier to the #submit (or any other v-on you're using), like this:
<form #submit.prevent="myMethod">
<button type="submit"></button>
</form>
In the case of submitting a form, this will prevent the default behavior of refreshing the page.
Long answer
There are several ways to modify events.
From the Vue 3 docs:
It is a very common need to call event.preventDefault() or
event.stopPropagation() inside event handlers. Although we can do this
easily inside methods, it would be better if the methods can be purely
about data logic rather than having to deal with DOM event details.
To address this problem, Vue provides event modifiers for v-on. Recall
that modifiers are directive postfixes denoted by a dot.
<!-- the click event's propagation will be stopped -->
<a #click.stop="doThis"></a>
<!-- the submit event will no longer reload the page -->
<form #submit.prevent="onSubmit"></form>
<!-- modifiers can be chained -->
<a #click.stop.prevent="doThat"></a>
<!-- just the modifier -->
<form #submit.prevent></form>
<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div #click.capture="doThis">...</div>
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div #click.self="doThat">...</div>
Another option:
Sometimes we also need to access the original DOM event in an inline statement handler. You can pass it into a method using the special $event variable:
<button #click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// now we have access to the native event
if (event) {
event.preventDefault()
}
alert(message)
}
}
Cheers :)
Didn't quite understand #Armin Ayari's answer, for instance why the code would have to be in the methods object? Anyway in Vue this is what worked for me:
<form ref="form" #submit.prevent="myMethod">
<button type="submit"></button>
</form>
This blocked the page from refreshing and called myMethod instead.
You don't even need the ref. Understood this is an old question, but I found myself here after debugging, and found my form tags were simply mis-placed.
I don't know if I understood your question correctly but you can prevent the default behavior of your form like this:
this.$refs.form.addEventListener("submit", (event) => {
event.preventDefault()
});
Maybe this can help you:
new Vue({
el: '#app',
data: {},
methods: {
submit () {
this.$refs.form.addEventListener('submit', event => {
event.preventDefault()
})
},
alert () {
alert('hello')
}
}
})
<body>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
<div class="form-wrapper" #click='submit'>
<form ref='form' #submit='alert'>
<input type="text">
<button type='submit'>Submit</button>
</form>
</div>
</div>
</body>
I think it's dona help!!
<form method="POST" action="http::localhost:8080/" #submit.prevent="submit_login($event)">
// enter yours inputs here
</form>
submit_login(e) {
if (true) {
e.target.submit();
},
},
After some proper investigation with not a single answer here being related to the original question.
I have found your solution, however it isn't VueJS specific, referencing this article: Javascript e.preventDefault(); not working on submit()
Answer
Your programmatic way to execute submit this.$refs.form.submit() isn't correct if you want to properly preventDefault() or even run other functions.
You need to run this.$refs.form.requestSubmit(), this replicates the functionality as if you would've had a child <button> run the clicked event.
First, don't use preventDefault method. I will illustrate this problem on jQuery:
$('#myForm').on('submit', function (event) {
// step 1.
// stop current action, prevent submitting
event.preventDefault()
// step 2.
// validate inputs
// some validation code
// step 3.
// everything ok, submit it
this.submit()
})
Where is problem with this code? When you submit this form programatically in step 3., it will be again captured and will stop at step 1. again. So, you will be never able to submit this form. Solution:
$('#myForm').on('submit', function (event) {
// step 1.
// validate inputs
// some validation code
// this code will be always executed
// before this form will be submitted
// step 2.
// then do something like this
// continue submitting form and exit
// this callback with returning true
if (inputsAre === 'ok') return true
// if inputs are not ok, program continues
// with following line, which ends this
// callback with false and form will be not submitted
return false
})
I hope you got the point. So, I think what you need is not the preventDefault method, but return true or return false in your doSomething method called on #submit event.
I'm trying to update the semantic ui dropdown with new values. Vue is correctly being updated and I'm refreshing the semantic ui dropdown but it still isn't updating. I saw another post which mentioned the use of key, but it still fails.
Template
<div id=root>
<label>Type:</label>
<select id="app_type" class="ui search selection dropdown" v-model="model_type_val">
<option v-for="model_type in model_types" v-bind:value="model_type.value" v-bind:key="model_type.value">{{model_type.text}}</option>
</select>
<p>
selected: {{model_type_val}}
</p>
</div>
Code
var model_types2= [
{value:"",text:"Type"},
{value:"type1",text:"Type1a"},
{value:"type2",text:"Type2a"},
{value:"type3",text:"Type3a"},
{value:"type4",text:"Type4"}
];
var vm2= new Vue({
el:'#root',
data:{
model_type_val:"",
model_types:[
{value:"",text:"Type"},
{value:"type1",text:"Type1"},
{value:"type2",text:"Type2"},
{value:"type3",text:"Type3"}
]
},
mounted: function(){
$('#app_type').dropdown();
setTimeout(function() {
this.model_types=model_types2;
alert(this.model_types[1].text);
$('#app_type').dropdown('refresh');
}, 1000);
}
});
I've tried to reproduce the code in this jsfiddle.
You have a this problem. When you have a callback inside a Vue method or lifecycle hook in which you use this, you need to make sure that this points to the correct object (the Vue). You do that with an arrow function, a closure, or bind.
setTimeout(() => {
this.model_types=model_types2;
$('#app_type').dropdown('refresh');
}, 1000);
Here is your fiddle updated.
Note: In the fiddle, I also converted your selector to use a ref. Typically you want to start weaning yourself off jQuery when working with Vue.
See How to access the correct this inside a callback.
This is a follow up to this question: Access a DOM element in Aurelia
Is there a hook in the Screen Activation Lifecycle which allows me to run code after ref bindings have been set up? Currently it seems like there is a period of time after the activate hook is called when the ref bindings are not set up yet and then at some point they get activated. I tested this by adding a <div ref="myDiv"></div> to near the bottom of welcome.html in a cloned version of the latest (v0.13.0) skeleton-navigation repo and testing the existence of the reference in the view-model like this:
export class Welcome{
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
testMyDiv() {
console.log("Getting my div")
console.log(this.myDiv)
}
get fullName(){
this.testMyDiv()
return `${this.firstName} ${this.lastName}`;
}
welcome(){
alert(`Welcome, ${this.fullName}!`);
}
}
A snippet of the bottom of the template...
<button type="submit" class="btn btn-default">Submit</button>
</form>
<div ref="myDiv"></div>
</section>
</template>
This is a snapshot of what I see in the console...
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 <div ref="myDiv" class="au-target"></div>
welcome.js:10 Getting my div
welcome.js:11 <div ref="myDiv" class="au-target"></div>
(continues)
The print outs like this goes on indefinitely. You can see that fullName() is being called regularly to update the screen if the name changes (I assume this is the dirty checking)... but you can see that at the beginning there is a period when the referenced div is NOT valid as a property of the view-model, and then it IS valid. Can someone explain this? Is there a way to hook into the view-model after the ref becomes valid?
In general, bindings are processed and available after the bind callback. However, in this case since you need to access the DOM element, you will need the ViewModel to be bound and attached to the view, so use the attached callback.
class ViewModel {
bind() {
this.refItem == undefined; // true
}
attached() {
this.refItem == undefined; // false
}
}
As you noted in the comments, more information on the activator callbacks is available here: http://aurelia.io/docs.html#extending-html
I am building an application with Durandal 2.0.
My shell view looks like this:
<div>
<header id="nav" data-bind="compose: 'viewmodels/nav', activate: false">
</header>
<section id="content" class="main container-fluid" data-bind="router: { transition: 'entrance' }, activate: false" style="overflow: auto">
</section>
<footer>
<!--ko compose: {view: 'footer'} --><!--/ko-->
</footer>
</div>
In the nav section, I want to have my tabs and a drop-down list of users (both of which are retrieved from a web service). Selecting a user from the dropdown will navigate to a new URL which will update the content section. (the route looks something like localhost/#user1/tab2).
Problem: I need to know the selected user from the nav section before I can retrieve the data for the content section, but the content section is activating before I have retrieved the users.
This is really only an issue for the initial page load, since the user list is only retrieved once.
Is there a way to tell the content section to wait until the nav section is done loading?
Is there a better way to go about this than what I'm doing?
The nav activate function looks like this:
function activate(context) {
return dataservice.getUsers().then(function () {
//do stuff
});
});
This activate function gets called first, and dataservice.getUsers() is called, but then the activate function of the content module gets called before the "do stuff" part happens (and before the data from the getUsers call is returned in the dataservice). Maybe there's a problem with my promises?
Edit
I've put together a dFiddle with some actual code that shows what I'm talking about: http://jerrade.github.io/dFiddle-2.0/#test/dashboard
The code is here: https://github.com/jerrade/dFiddle-2.0
nav.js
function activate(context) {
console.log('Nav View Activated');
if (vm.impersonateUsername == undefined)
vm.impersonateUsername = getUsernameFromWindowLocation();
return dataservice.getPageDetailForEmployee(vm.loggedInUsername, vm).then(function () {
console.log("Page details retrieved");
// I want to do something here before the dashboard activates.
});
}
dashboard.js
function activate(username) {
console.log('Dashboard View Activated');
//vm.username = nav.selectedImpersonateEmployee().Username;
return dataservice.getDashboard(nav.impersonateUsername, dashboard);
}
Open the page and watch the console. You'll see (among other things)
Nav View Activated
Dashboard View Activated
Page details retrieved
What I really want is for the Page details to be retrieved before the Dashboard view activates. I've actually rejiggered things so that this isn't currently a problem anymore, but it may crop again down the road.
It doesn't seem like what I'm trying to do should be this complicated. Unless I'm pounding a square peg into a round hole here?
The easiest thing to do would be to add an observable called something like isUserLoaded on your view model. Then, apply an if binding around the content section:
<div>
<header id="nav" data-bind="compose: 'viewmodels/nav', activate: false">
</header>
<!-- ko if: isUserLoaded -->
<section id="content" class="main container-fluid" data-bind="router: { transition: 'entrance' }, activate: false" style="overflow: auto">
</section>
<!-- /ko -->
<footer>
<!--ko compose: {view: 'footer'} --><!--/ko-->
</footer>
</div>
Once you've loaded the user, you can update the observable to true and your content binding should fire.
Edit
If that does't work it sounds like your router must still be resolving the content view model (which may point to a routing problem? Hard to say without seeing your entire solution).
Anyway, if its not a routing issue, then you could employ a solution where your content model returns an unresolved promise from its activate method; then resolve that promise when the user is loaded. For example, something along these lines:
userModel.js:
define([], function () {
// single instance to track the loaded user
var userModel = {
selectedUser: ko.observable()
};
return userModel;
});
navModel.js:
define(["dataService", "userModel"], function(dataService, userModel) {
// view model for the nav bar
var navModel = {
// model definition
};
// load the user
navModel.activate = function() {
return dataService.getUsers().then(function(response) {
// Push the user to the userModel
userModel.selectedUser(response.user);
});
};
return navModel;
});
contentModel.js:
define(["userModel"], function (userModel) {
var contentModel = {
// model definition
};
contentModel.activate = function () {
// Prevent activation until user is resolved
var deferred = $.Deferred();
var subscription = userModel.selectedUser.subscribe(function (user) {
if (!user)
return;
subscription.dispose();
deferred.resolve();
});
return deferred.promise();
};
return contentModel;
});
If you don't want to use an observable for the userModel.selectedUser property, you could instead use another deferred, or fire an event when the user is loaded, etc etc. Whatever floats your boat!