Durandal navigation: attr href binding not working - durandal

I have the following setup in Durandal for navigation:
layout.js
...
that.activate = function () {
router.map([
{ route: '', title: abp.localization.localize('HomePage', 'MyProject'), moduleId: 'viewmodels/home', nav: true, menuName: 'Home' },
{ route: 'editpage/:id', title: abp.localization.localize('EditPage', 'MyProject'), moduleId: 'viewmodels/editpage', hash: '#/editpage/id' }
]).buildNavigationModel();
pages.js
...
that.editPage = function (page) {
that.router.navigate('editpage/' + page.id());
};
pages.cshtml
<ul class="list-group" data-bind="foreach: pages">
<div class="list-group-item">
<span class="page-name" data-bind="text: name"></span>
<div class="text-right">
<!--<button type="button" class="btn btn-info btn-sm" data-bind="click: $parent.editPage">-->
<button type="button" class="btn btn-info btn-sm" data-bind="attr: {href: '#/editpage/' + id()}">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> #L("EditPage")
</button>
</div>
</div>
</ul>
I'd like to navigate to the "EditPage/:id" page when I hit the Edit Page button. However only the click binding works:
<button type="button" class="btn btn-info btn-sm" data-bind="click: $parent.editPage">
The attr href binding does not:
<button type="button" class="btn btn-info btn-sm" data-bind="attr: {href: '#/editpage/' + id()}">
I tried several ways of configuration but none of them seemed to help. I guess its just an improper syntax in the route configuration or in the hash. Couldn't figure it out.
Any help would be appreciated!

Try this
<a class="btn btn-info btn-sm" data-bind="attr: { href: '#editpage/' + id() }"></a>
1) Use href attr with a tag instead of button
2) Removed unnecessary / after #
#/editpage/ changed to #editpage/

I haven't used the hash parameter before. However, according to the router documentation (http://durandaljs.com/documentation/Using-The-Router.html), the hash you're looking for here is literally #/editpage/id and the hash you're passing is '#/editpage/' + id(). Try writing the following instead:
Router:
{ route: 'editpage/:id', title: abp.localization.localize('EditPage', 'MyProject'), moduleId: 'viewmodels/editpage', hash: '#editpage/:id' }
Page
<button type="button" class="btn btn-info btn-sm" data-bind="attr: {href: '#editpage/' + id()}">
Additionally, if you simply hyperlink to the route (as you have done) and remove the hash parameter from the route, you should be fine.

Related

Vue's reactivity not triggered when using Bootstrap 5's alert div

Vue's reactivity not triggered when using Bootstrap 5's alert div.
See my code:
<template>
<div>
<div
v-if="alertICDMsg!==''"
id="alertICDCode"
class="alert alert-info alert-dismissible fade show"
role="alert"
>
<i class="fa-solid fa-lightbulb" />
<strong> Note!</strong> <span v-html="alertICDMsg" />
<button
type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"
/>
</div>
<div class="input-group mb-3">
<div class="col-xs-1">
<input
id="ICDCode"
v-model="editing_icdCode"
class="form-control"
placeholder="ICD Code"
aria-label="ICD Code"
#input="ICDCodeSearchRequested"
>
</div>
<input
id="Diagnosis"
v-model="editing_diagnosis"
type="text"
class="form-control"
placeholder="Diagnosis"
aria-label="Diagnosis"
aria-describedby="basic-addon1"
list="icdsearchlist"
#change="SelectedDiagnosisTextOption"
#input="ICDTextSearchRequested"
>
<datalist id="icdsearchlist">
<option
v-for="(disease_option, index) in icd_diagnosis_options"
:key="index"
>
{{ disease_option }}
</option>
</datalist>
<button
id="btnAddDiagnoses"
href="#"
class="btn btn-primary mx-1"
#click="AddDiagnosis"
>
<i class="fal fa-plus-circle" />
</button>
<button
id="btnCopyPreviousDiagnoses"
href="#"
class="btn btn-primary BtnSaveGroup mx-1"
>
<i class="far fa-history" />
</button>
<button
id="quickbill"
class="btn btn-primary mx-1"
>
<i class="fas fa-search-plus" />
</button>
<button
id="clearICD"
class="btn btn-danger mx-1"
#click="ClearICDFields"
>
<i class="fad fa-times-circle" />
</button>
</div>
</div>
<template>
<script>
export default {
data() {
return {
alertICDMsg:"",
};
},
watch: {
alertICDMsg: {
handler(val) {
console.log(`Val for alertICDMsg changed to :${val}`);
},
immediate: true,
deep: true,
},
},
methods: {
ICDCodeSearchRequested() {
console.log(`Search by ICD code`);
this.alertICDMsg="Searching in ICD Code box will search only by code. To search by a diagnosis name, type in the Diagnosis box."
console.log(`alertICDMsg is ${this.alertICDMsg}`);
setTimeout(function() {
console.log(`Dismissing alert`);
this.alertICDMsg='';
console.log(`alertICDMsg is ${this.alertICDMsg}`);
}, 5000);
},
},
}
</script>
Console log:
Search by ICD code
SubClinicalBlock.vue?d801:291 alertICDMsg is Searching in ICD Code box will search only by code. To search by a diagnosis name, type in the Diagnosis box.
SubClinicalBlock.vue?d801:220 Val for alertICDMsg changed to :Searching in ICD Code box will search only by code. To search by a diagnosis name, type in the Diagnosis box.
SubClinicalBlock.vue?d801:293 Dismissing alert
SubClinicalBlock.vue?d801:298 alertICDMsg is
The problem is that after 5 seconds, though the value of the variable changes, the alert is still visible.
I checked some similiar questions, and have seen this happening when bootstrap's javascript wasnt loaded. But for me, Bootstrap v5.0.1 JS is being loaded from the CDN and appears in the sources tab in Chrome.
Try to change the function inside of setTimeout to arrow function like this
setTimeout(() => { // code here })
The this inside of setTimeout(function () => {}) reference to the wrong context (the function itself) instead of the Vue component.
The arrow function doesn't have the this binding so when you use the arrow function the this keyword will reference the Vue component and change the state.
More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

nesting an object inside an object

Looking for some tips on how to nest objects inside objects using a form. My form currently changes the key and value of an object. However, I'm now wanting a second button to be able to create a child (correct termanology?)form input. below you can see an example. I've spent the morning looking at props but I'm unsure if this is the correct way to go, any suggestions are greatly appriciated
{
"color": "black",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,255,1],
"hex": "#000"
}
},
<form id="app">
<h1>
Title goes here
</h1>
<hr>
<div class="row">
<div class="col-xs-2">
<button type="button" v-on:click="addNewObject" class="btn btn-block btn-success">
(Add +) Parent
</button>
</div>
<div class="col-xs-10 text_info">
Click 'Add +' to add an object
</div>
</div>
<div v-for="(object, index) in objects">
<div class="row">
<div class="col-xs-1">
<label> </label>
<button type="button" v-on:click="removeObject(index)" class="btn btn-rem btn-block btn-danger">
Delete
</button>
<button type="button" v-on:click="addNewChildObject()" class="btn btn-rem btn-block btn-success btn-suc">
add { }
</button>
</div>
<div class="form-group col-xs-7">
<div class="test">
<select v-model="object.type" class="selectBox classic">
<option value="" disabled selected hidden>Choose Datatype</option>
<option v-for="type in types"> {{ type }}</option>
</select>
<input v-model="object.name" :name="'objects[' + index + '][name]'" type="string" class="form-control" placeholder="Enter key">
<input v-model="object.dataValue" :name="'objects[' + index + '][dataValue]'" type="string" class="form-control" placeholder="Enter value">
</div>
</div>
</div>
</div>
<hr>
<div>
<pre v-if="seen">{{ mappedObjects }}</pre>
</div>
<button type="button" class="btn-primary" v-on:click="seen = !seen">{{ seen ? 'Click to Hide the JSON' : 'Click to Show the JSON' }}</button>
</form>
const getDefaultObject = () => ({
name: '',
dataValue: '',
type: ''
})
const app = new Vue({
el: '#app',
computed: {
mappedObjects() {
return this.objects.map(({
name,
dataValue,
type
}) => ({
[name]: dataValue,
type
}))
}
},
props: {
},
data() {
return {
seen: false,
types: ['string', 'character', 'number', 'int', 'floating-point', 'boolean', 'date;'],
objects: []
}
},
methods: {
addNewObject: function() {
this.objects.push(getDefaultObject())
},
removeObject: function(index) {
Vue.delete(this.objects, index);
},
addNewChildObject: function () {
}
}
})
If you want n forms with n children just create a model for it following that same structure and pass props to the form.
parent = {
...props,
children: []
}
child = {
...props
}
If the forms are too complex (or a little complex really), split them in separate components and pass children as props.
If you want to use the same form both in parent and children take a look at slots, they will allow you to create flexible layouts.

How to replace div with html onclick using Vue js?

I have a Respond Button inside a div with a Modal attached to it.
<div v-if="notification ? notification.type === 'App\\Notifications\\InterviewRequestEmployerReply' : ''">
<button class="btn btn-primary btn-sm text-right" #click="editNotificationRequest(notification)">Respond</button>
</div>
If a User clicks the 'Confirm Interview' button, The Respond Button should be replaced with this <strong class="text-success">Confirmed</strong>.
I have a method called createConfirmedInterviewRequest() that gets called when the User clicks on the 'Confirm Interview' button. So I'd like to create a method that will change the contents and then I would just call that method inside my createConfirmedInterviewRequest() method upon Success, but I don't know how to create this method or how this can be done.
Attached is a scrrenshot of my page with the Respond Button and Modal.
How can I do change the contents of the div using Vue?
UPDATED:
data() {
return {
notifications: {
noti: false
},
}
},
computed:{
computedConfirm(){
return this.notifications.noti ? true:false;
}
},
methods: {
confirmProcess(){
this.notifications.noti = true;
},
}
My method:
createConfirmedInterviewRequest: async function() {
this.confirmProcess(`data`);
}
Modal with button:
<b-button type="submit" variant="success" #click="confirmProcess(`data`)" class="mr-2 mt-2">
<i class="fas fa-handshake"></i>Confirm Interview
</b-button>
Respond button I want to disappear and substitute with <strong class="text-success">Confirmed</strong>.
<div v-if="notification ? notification.type === 'App\\Notifications\\InterviewRequestEmployerReply' : ''">
<button class="btn btn-primary btn-sm text-right" #click="editNotificationRequest(notification)" v-if="!computedConfirm">Respond</button>
Let me know if you need to know what data properties are inside of notifications.
Use v-if , v-else, condition will change in your editNotificationRequest method:
<div v-if="notification ? notification.type === 'App\\Notifications\\InterviewRequestEmployerReply' : ''">
<button v-if="clickConfirmed" class="btn btn-primary btn-sm text-right" #click="editNotificationRequest(notification)">Respond</button>
<strong v-else class="text-success">Confirmed</strong>
</div>

Click Event on Dynamically Generated Button Don't get fired in Vue

I am adding a button dynamically and attaching the click event but it doesn't seem to fire.
I see something similar on link below but its not exactly what I am looking for.
Vue: Bind click event to dynamically inserted content
let importListComponent = new Vue({
el: '#import-list-component',
data: {
files: [],
},
methods: {
// more methods here from 1 to 5
//6. dynamically create Card and Commit Button
showData: function (responseData) {
let self = this;
responseData.forEach((bmaSourceLog) => {
$('#accordionOne').append(`<div class="main-card mb-1 card">
<div class="card-header" id=heading${bmaSourceLog.bmaSourceLogId}>
${bmaSourceLog.fileName}
<div class="btn-actions-pane-right actions-icon-btn">
<input type="button" class="btn btn-outline-primary mr-2" value="Commit" v-on:click="commit(${bmaSourceLog.bmaSourceLogId})" />
<a data-toggle="collapse" data-target="#collapse${ bmaSourceLog.bmaSourceLogId}" aria-expanded="false" aria-controls="collapse${bmaSourceLog.bmaSourceLogId}" class="btn-icon btn-icon-only btn btn-link">
</a>
</div>
</div>
<div id="collapse${ bmaSourceLog.bmaSourceLogId}" class="collapse show" aria-labelledby="heading${bmaSourceLog.bmaSourceLogId}" data-parent="#accordionOne">
<div class="card-body">
<div id="grid${ bmaSourceLog.bmaSourceLogId}" style="margin-bottom:30px"></div>
</div>
</div>
</div>`);
});
},
//7. Commit Staging data
commit: function (responseData) {
snackbar("Data Saved Successfully...", "bg-success");
},
}});
I am adding button Commit as shown in code and want commit: function (responseData) to fire.
I was able to achieve this by pure Vue way. So my requirement was dynamically add content with a button and call a function from the button. I have achieved it like so.
Component Code
const users = [
{
id: 1,
name: 'James',
},
{
id: 2,
name: 'Fatima',
},
{
id: 3,
name: 'Xin',
}]
Vue.component('user-component', {
template: `
<div class="main-card mb-1 card">
<div class="card-header">
Component Header
<div class="btn-actions-pane-right actions-icon-btn">
<input type="button" class="btn btn-outline-primary mr-2" value="Click Me" v-on:click="testme(user.id)" />
</div>
</div>
<div class="card-body">
{{user.name}}
</div>
<div class="card-footer">
{{user.id}}
</div>
</div>
`
,props: {
user: Object
}
,
methods: {
testme: function (id) {
console.log(id);
}
}});
let tc = new Vue({
el: '#test-component',
data: {
users
},});
HTML
<div id="test-component">
<user-component v-for="user in users" v-bind:key="user.id" :user="user" />
</div>

how to toggle class for multiple elements in vue js

I'm trying to toggle classes for multiple elements in Vuejs 2.0, I have the following set of buttons which has a class of btn-primary. Clicking a button shows a sub-group of that particular element. This is my code:
<button class="btn btn-primary btn-xs" v-on:click.prevent="getTags('investor')">Investor</button>
<button class="btn btn-primary btn-xs" v-on:click.prevent="getTags('research')">Research</button>
<button class="btn btn-primary btn-xs" v-on:click.prevent="getTags('company')">Company</button>
This shows the following element:
<div v-if="tag.investor">
<button class="btn btn-primary btn-xs" v-on:click.prevent="selectTags('Investor - Mutual Funds')">Mutual Fund</button>
<button class="btn btn-primary btn-xs" v-on:click.prevent="selectTags('Investor - Insurance')">Insurance</button>
<button class="btn btn-primary btn-xs" v-on:click.prevent="selectTags('Investor - FII')">FII</button>
</div>
<div v-if="tag.research">
<button class="btn btn-primary btn-xs" v-on:click.prevent="selectTags('Research - Tier I')">Research - Tier I</button>
<button class="btn btn-primary btn-xs" v-on:click.prevent="selectTags('Research - Tier II')">Research - Tier II</button>
</div>
I have the following in data():
tag: {
investor: false,
research: false,
company: false,
others: false,
},
And in methods:
getTags: function (tag) {
this.tag.investor = this.tag.research = this.tag.company = this.tag.others = false
if(tag == 'investor')
{
this.tag.investor = true
}
if(tag == 'research')
{
this.tag.research = true
}
if(tag == 'company')
{
this.tag.company = true
}
if(tag == 'others')
{
this.tag.others = true
}
},
I want to have a class of btn-warning and remove btn-primary once any child element is being selected. Any ideas how to implement this, I don't want to have individual data elements and toggle class.
I'd like to suggest a data driven approach for your Vue. Consider this data structure:
const tags = {
Investor:[
{display:"Mutual Fund", value:"Investor - Mutual Funds"},
{display:"Insurance", value:"Investor - Insurance"},
{display:"FII", value:"Investor - FII"},
],
Research:[
{display:"Research - Tier I", value:"Research - Tier I"},
{display:"Research - Tier II", value:"Research - Tier II"},
]
}
If you use that, then you can clean up your template considerably and handle any additional tags that you add to your data structure.
<div id="app">
<button v-for="(obj, key) in tags"
:key="key"
#click="currentTag = key"
class="btn btn-xs btn-primary">
{{key}}
</button>
<div>
<button v-for="tag in tags[currentTag]"
:key="tag"
class="btn btn-xs"
:class="tagClass(tag)"
#click="selectTags(tag.value)">
{{tag.display}}
</button>
</div>
</div>
Your Vue also looks a lot cleaner.
new Vue({
el:"#app",
data:{
currentTag: null,
tags,
selectedTags:[]
},
methods:{
selectTags(tag){
const index = this.selectedTags.findIndex(t => t == tag)
if (index >= 0)
this.selectedTags.splice(index, 1)
else
this.selectedTags.push(tag)
},
tagClass(tag){
const isSelected = this.selectedTags.includes(tag.value)
return {
'btn-warning': isSelected,
'btn-primary': !isSelected
}
}
}
})
Here is an example.
You can use the v-bind:class directive.
<button
class="btn btn-xs"
v-bind:class="{ 'btn-warning': tag.research, 'btn-primary': !tag.research }"
v-on:click.prevent="selectTags('Research - Tier I')"
>
Research - Tier I
</button>