VUE JS - Some of my methods are being called without me calling them - javascript

I'm having an annoying issue with Vue JS >.< My methods are being called without my approval. I basically have a button that execute a method, but this method execute when other methods are executed, making it very annoying...
Here is my form
<form class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 pr-xl-0 pr-lg-0 pr-md-0 m-b-30">
<div class="product-slider">
<img class="d-block" :src="image" alt="First slide" width="285" height="313">
Image URL <input type="text" #focusout="showPicture" id="imageLink">
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 pl-xl-0 pl-lg-0 pl-md-0 border-left m-b-30">
<div class="product-details">
<div class="border-bottom pb-3 mb-3">
<h2 class="mb-3">
<input type="text" value="Product Name" minlength="4" id="name" required/>
</h2>
<h3 class="mb-0 text-primary">$<input type="number" value="1.00" step="0.01" id="price" required></h3>
</div>
<div class="product-size border-bottom">
<h4>Provider</h4>
<input type="text" value="Pro Inc." minlength="3" id="provider" required>
<div class="product-qty">
<h4>Quantity</h4>
<div class="quantity">
<input type="number" value="1" id="quantity" required>
</div>
</div>
</div>
<div class="product-description">
<h4 class="mb-1">Description</h4>
<textarea rows="4" cols="50" minlength="50" id="description" required>Sample Text</textarea>
<button :onclick="addProduct()" class="btn btn-primary btn-block btn-lg">Add to inventory</button>
</div>
</div>
</div>
</form>
and here is my full script
<script>
const DB_NAME = 'DBInventory';
const DB_VERSION = 1;
export default {
data: function() {
return {
db:null,
ready:false,
addDisabled:false,
image: "https://i.imgur.com/O9oZoje.png",
};
},
async created() {
this.db = await this.getDb();
this.ready = true;
},
methods: {
showPicture() {
let link = document.getElementById("imageLink").value;
if(link !== "")
this.image = link;
},
async getDb() {
return new Promise((resolve, reject) => {
let request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = e => {
console.log('Error opening db', e);
reject('Error');
};
request.onsuccess = e => {
resolve(e.target.result);
};
request.onupgradeneeded = e => {
console.log('onupgradeneeded');
let db = e.target.result;
let objectStore = db.createObjectStore("products", { autoIncrement: true, keyPath:'id' });
console.log(objectStore);
};
});
},
async addProduct() {
this.addDisabled = true;
let product = {
name: document.getElementById("name").value,
provider: document.getElementById("provider").value,
price: document.getElementById("price").value,
quantity: document.getElementById("quantity").value,
description: document.getElementById("description").value,
image: document.getElementById("imageLink").value,
};
console.log('about to add '+JSON.stringify(product));
await this.addProductToDb(product);
this.addDisabled = false;
},
async addProductToDb(product) {
return new Promise((resolve, reject) => {
//delete me
console.log(reject);
let trans = this.db.transaction(['products'],'readwrite');
trans.oncomplete = e => {
//delete me
console.log(e);
resolve();
};
let store = trans.objectStore('products');
store.add(product);
});
},
}
}
</script>
One of my method execute when you are not focused on the image input field. It works, but also execute the addProduct(), which push my item to my indexDB, something that I want to happen only when I press the button "Add to inventory".
This is very confusing and I'm kinda a noob on Vue JS ^^' (I use Vue 3)

You have the wrong syntax
:onclick="addProduct()"
:onclick should be #click or v-on:click
https://v2.vuejs.org/v2/guide/events.html

To avoid the function to be autoexecuted, you need to provide the onclick method with the function declaration. Try the code below. I don't use Vue, but it works in React. Javascript anyway.
:onclick="() => addProduct()"

Correct syntax for click event in vue
full syntax
<a v-on:click="addProduct"></a>
shorthand syntax
<a #click="addProduct"></a>
Then call method addProduct
addProduct: function () {
...
}

Related

Stripe.js payment does not work for some customers only

I have a php (Laravel 7) website that uses Stripe.js to accept credit card payments. The integration usually works nicely, but from time to time, I have some customers, who are not able to make a payment. There is no error message, all I know from the customers is something like this:
"I entered the card details, clicked "Pay" and then a white field and nothing..."
On the stripe backend, I see the payment with status payment "Incomplete" and the "Customer has not entered its payment method". I believe this means that the customer came to the payment page, but has not (could not?) finished the payment for some reason.
Please see some code excerpts below. The first code shows how I create a payment intent when the payment page is loaded. The second one shows the payment form and js code to make the payment. My suspicion is that somehow the js code breaks down for some customers...
Any ideas why this is not working for some customers or how I could debug the root-cause?
Thanks,
W.
GNGBookingController.php:
function show_step4($id){
// Create and send payment intent
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
$idstr = '[T#' . sprintf('%04d', $tour->id) . ']';
try{
$intent = \Stripe\PaymentIntent::create([
'amount' => round($tour->fees->plat_fee * 100),
'currency' => 'eur',
'payment_method_types' => ['card'],
'description' => $idstr,
]);
}
catch(\Exception $e) {
GNGLogger::qlog('Failed to create payment intent. ' . $e->getFile() . ' ' . $e->getLine() . $e->getMessage(), GNGLogType::Error);
redirect(route('office.' . app::getLocale() . '.crash.show'));
}
return view('backoffice.booking.ver2.show-step4', [
'tour' => $tour,
'clientSecret' => $intent->client_secret,
'date' => $date,
'time' => $time,
]);
}
show-step4.blade.php:
#extends('backoffice.layouts.guest')
#section('content')
<script src="https://js.stripe.com/v3/"></script>
<div class="row">
<div class="col-sm-1 col-md-2 col-lg-3 col-xl-4">
</div>
<div class="col">
<h3>{{__('Advance Payment')}}</h3>
<form id="payment-form" method="post" data-secret="{{$clientSecret}}">
<input type="hidden" name="token" />
<div id="payment-form-holder" class="my-4 p-3 border rounded shadow" style="background-color: lightgrey">
#csrf
<div class="form-group">
<input type="hidden" class="form-control" name="ID" id="ID" value="{{$tour->id}}">
</div>
<div class="form-group">
<label>{{__('Card Number')}}</label><br>
<div id="card-number">
</div>
</div>
<div class="form-row">
<div class='col'>
<div class="form-group">
<label>{{__('Expiry Date')}}</label><br>
<div id="card-expiry">
</div>
</div>
</div>
<div class='col'>
<label>{{__('Security Code')}}</label><br>
<div class="form-group">
<div id="card-cvc">
</div>
</div>
</div>
</div>
<!-- We'll put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<div class="form-row my-3" style="flex-wrap: nowrap">
<div class="spinner-border text-danger" id="spinner" role="status" style="display: none;">
</div>
<div id="spinner-text" class="mx-2" style="display: none;">
{{__('Please wait. This can take up to a minute...')}}
</div>
</div>
<p id="card-error" role="alert"></p>
<p id="result-message" class="result-message" style="display: none;">
{{__('Payment succeeded!')}}
</p>
<div class="form-row">
<input type="submit" id="pay" name="pay" class="btn btn-primary gng-action-button mr-3 mb-3" style="min-width: 100%" value="{{__('Pay :platFee €', ['platFee' => $tour->fees->plat_fee])}}" >
</div>
</div>
<input type="submit" id="back" name="back" class="btn btn-secondary gng-action-button mr-3 mb-3" value="{{__('Back')}}" >
</form>
</div>
<div class="col-sm-1 col-md-2 col-lg-3 col-xl-4">
</div>
</div>
#endsection
#section('scripts')
<script>
// Create a Stripe client
var stripe = Stripe('{{env('STRIPE_KEY')}}');
// Set up Stripe.js and Elements to use in checkout form
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
fontSize: "18px",
}
};
var classes = {
base: 'form-control',
};
var cardNumberElement = elements.create('cardNumber', {
style: style,
classes: classes,
});
cardNumberElement.mount('#card-number');
var cardExpiryElement = elements.create('cardExpiry', {
style: style,
classes: classes,
});
cardExpiryElement.mount('#card-expiry');
var cardCvcElement = elements.create('cardCvc', {
style: style,
classes: classes,
});
cardCvcElement.mount('#card-cvc');
document.querySelector('#payment-form').addEventListener('submit', function(e) {
if (e.submitter.name == 'pay') {
e.preventDefault();
loading(true);
stripe
.confirmCardPayment('{{$clientSecret}}', {
payment_method: {
card: cardNumberElement,
billing_details: {
name: '{{$tour->guest->name}}'
}
}
})
.then(function(result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment succeeded!
orderComplete(result.paymentIntent.id);
stripe.createToken(cardNumberElement, options).then(submitToken);
}
});
var options = {
//address_zip: document.getElementById('postal-code').value,
};
}
});
function submitToken(result) {
var form = document.querySelector('#payment-form');
form.querySelector('input[name="token"]').setAttribute('value', result.token.id);
form.submit();
}
/* ------- UI helpers ------- */
// Shows a success message when the payment is complete
var orderComplete = function(paymentIntentId) {
loading(false);
document.querySelector(".result-message").style.display = 'flex';
document.querySelector("#pay").disabled = true;
document.querySelector("#back").disabled = true;
};
// Show the customer the error from Stripe if their card fails to charge
var showError = function(errorMsgText) {
loading(false);
var errorMsg = document.querySelector("#card-error");
errorMsg.textContent = errorMsgText;
setTimeout(function() {
errorMsg.textContent = "";
}, 4000);
};
// Show a spinner on payment submission
var loading = function(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#pay").disabled = true;
document.querySelector("#back").disabled = true;
document.querySelector("#spinner").style.display = 'flex';
document.querySelector("#spinner-text").style.display = 'flex';
document.querySelector("#pay").style.display = 'none';
document.querySelector("#back").style.display = 'none';
} else {
document.querySelector("#pay").disabled = false;
document.querySelector("#back").disabled = false;
document.querySelector("#spinner").style.display = 'none';
document.querySelector("#spinner-text").style.display = 'none';
document.querySelector("#pay").style.display = 'inline-block';
document.querySelector("#back").style.display = 'inline-block';
}
};
</script>
#endsection
If it is only breaking for some customers, I suspect this is a device/browser compatibility issue. Whenever this happens, be sure to ask what device and browser they are using when submitting payment. Then, you can verify if they are using a stripe.js compatible browser. If they are using a stripe.js compatible browser, my recommendation is to install whatever browser they are using and test your flow (while inspecting the developer console for errors).

is there a way to add a textarea to function with a floating label in this case?

I have a simple for with 3 fields using vue.js, I am setting up "floating labels" on focus, as of now I have the inputs working nicely adding and removing class "active" as expected. When I try to add "textarea" my app is breaking and returning "cannot read value of null", is there a way to add the textarea as the inputs are setup?
html:
<form #submit.prevent="submitForm()">
<div class="form-group">
<div id="floatWrapper" class="float-wrapper">
<label for="floatField">Enter Your Name</label>
<input
id="floatField"
v-model="contact_name"
type="text"
class="form-control form-control--email"
#input="$v.contact_name.$touch"
/>
</div>
<p v-if="$v.contact_name.$dirty">
<span v-if="!$v.contact_name.required" class="form__alert">
Please enter your name.
</span>
</p>
<div id="floatWrapper" class="float-wrapper">
<label for="floatField">Enter Your Email</label>
<input
v-model="contact_email"
type="email"
class="form-control form-control--email"
/>
</div>
<p v-if="$v.contact_email.$dirty">
<span v-if="!$v.contact_email.required" class="form__alert">
Email is required.
</span>
<span v-if="!$v.contact_email.email" class="form__alert">
Please enter a valid email
</span>
</p>
<div id="floatWrapper" class="float-wrapper">
<label for="floatField">Enter Your Message</label>
<textarea
v-model="contact_message"
class="form-control form-control--textarea"
rows="5"
/>
</div>
<p v-if="$v.contact_message.$dirty">
<span
v-if="!$v.contact_message.required"
class="form__alert"
>
Message must be over 10 characters
</span>
</p>
</div>
<button
type="submit"
data-cursor="hover"
class="btn btn-primary"
>
Send Message
<font-awesome-icon far icon="arrow-right" />
</button>
</form>
js:
mounted() {
const labelFloating = (() => {
const isFocus = (e) => {
const target = e.target
target.parentNode.classList.add('active')
}
const isBlur = (e) => {
const target = e.target
if (!target.value) {
target.parentNode.classList.remove('active')
}
}
const bindEvents = (element) => {
const field = element.querySelector('input')
field.addEventListener('focus', isFocus)
field.addEventListener('blur', isBlur)
const text = element.querySelector('textarea')
text.addEventListener('focus', isFocus)
text.addEventListener('blur', isBlur)
}
const initialize = () => {
const floatWraps = document.querySelectorAll('.float-wrapper')
floatWraps.forEach((element) => {
if (element.querySelector('input').value) {
element.classList.add('active')
} if (element.querySelector('textarea').value) {
element.classList.add('active')
}
bindEvents(element)
})
}
return {
init,
}
})()
labelFloating.init()
},
The if (element.querySelector('input').value) { will throw that error because you're chaining .value off the selector, but it doesn't exist and returns null. try this instead:
const selector = element.querySelector('input') || element.querySelector('textarea')
if (selector && selector.value) {
element.classList.add('active')
bindEvents(element)
}
And use the same refactor for the bindEvents method:
const bindEvents = (element) => {
const field = element.querySelector('input') || element.querySelector('textrea')
field.addEventListener('focus', isFocus)
field.addEventListener('blur', isBlur)
}

Laravel - Image File Uploader submits save button instead of uploading image

I am using Laravel-5.8 for my project. I want to upload image, and click on submit button to save the image and other text fields into the database.
Controller
public function create()
{
$countries = ConfigCountries::all();
return view('organization.companies.create')->with('countries', $countries);
}
public function store(Request $request)
{
try {
$orgStartDate = Carbon::parse($request->org_start_date);
$arr = [
'organisation_code' => $request->organisation_code,
'organisation_name' => $request->organisation_name,
'website' => $request->website,
'org_decription' => $request->org_description,
'total_employees' => $request->total_employees,
'registration_number' => $request->registration_number,
'org_start_date' => $request->$orgStartDate,
'phone_number' => $request->phone_number,
'secondary_phone' => $request->secondary_phone,
'email' => $request->email,
'secondary_email' => $request->secondary_email,
'address1' => $request->address1,
'address2' => $request->address2,
'country_id' => $request->country_id,
'created_by' => Auth::user()->id,
'created_at' => date("Y-m-d H:i:s"),
'is_active' => 1,
];
if ($request->org_image != "") {
$org_image = time() . '_' . $request->org_image->getClientOriginalName();
$request->org_image->move('storage_files/companies/profile/', $org_image);
$arr['org_image'] = 'storage_files/companies/profile/' . $org_image;
}
$company = OrgCompany::create($arr);
$company->save();
Session::flash('success', 'Company is created successfully');
return redirect()->route('organization.companies.index');
} catch (Exception $exception) {
return back()->withInput()
->withErrors(['unexpected_error' => 'Unexpected error occurred while trying to process your request.']);
}
}
view
<div class="card-body">
<form action="{{ route("organization.companies.store") }}" method="post class="form-horizontal" enctype="multipart/form-data">
{{csrf_field()}}
<div class="text-center">
<input type="image" class="profile-user-img img-fluid img-circle"
src="{{asset('theme/adminlte3/dist/img/company_logo.png')}}"
id="wizardPicturePreview" title="" width="150">
<input type="file" name="picture" id="wizard-picture" class="" hidden>
<h4 class="profile-username text-center">Click On Image to Add Logo</h4>
</div>
<span style="color:blue;"><h4 class="box-title"><b>Contact Information</b></h4></span>
<hr class="m-t-0 m-b-40">
<div class="row">
<div class="col-md-6">
<div class="form-group row">
<label class="control-label text-right col-md-3"> Phone No.</label>
<div class="col-md-9 controls">
<input type="text" name="phone_number" placeholder="Enter Company Phone no. here" class="form-control" value="{{old('phone_number')}}">
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group row">
<label class="control-label text-right col-md-3"> Alternative Phone No.</label>
<div class="col-md-9 controls">
<input type="text" name="secondary_phone" placeholder="Enter Alternative Phone no. here" class="form-control" value="{{old('secondary_phone')}}">
</div>
</div>
</div>
<!--/span-->
</div>
<hr>
<div class="form-actions">
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-offset-3 col-md-9">
<button type="submit" class="btn btn-primary">Add Company</button>
<button type="button" onclick="window.location.href='{{route('organization.companies.index')}}'" class="btn btn-default">Cancel</button>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<script>
$(document).ready(function(){
$("#wizard-picture").change(function(){
readURL(this);
});
});
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#wizardPicturePreview').attr('src', e.target.result).fadeIn('slow');
}
reader.readAsDataURL(input.files[0]);
} }
$("input[type='image']").click(function() {
$("input[id='wizard-picture']").click();
});
$(".form-control").keypress(function(e) {
if (e.which == 13) {
e.preventDefault();
return false;
}
});
</script>
The issue is that without clicking on the save submit button, when I clicked on image file uploader, it redirects to the index page without uploading the image to the screen.
How do I resolve this?
Thanks
An input type of image is a way to define an image as a submit button, so clicking on the image actually is submitting the form. You can either change the image to a regular <img> tag or change you click handler to prevent the default action like so
$("input[type='image']").click(function(e) {
e.preventDefault();
$("input[id='wizard-picture']").click();
});
Also just a heads up, you have a typo in the line that opens your form. There's no closing quotes after method="POST which is probably why the page is just redirecting on submit
Maybe because the closing quote of method is missing in <form> starting tag

Vue.js: A value in a v-for loop is not staying with the correct array items

I am trying to create a simple application to request a car key for a service department. Obviously the code could be written better, but this is my third day with Vue.js. The time function that is called in the first p tag in the code updates every minutes to keep count of an elapsed time. The problem I am having is when I request a new key the time function doesn't follow the array items as intended. For example, if there are no other requests the first request I submit works perfectly. However, when I submit a new request the elapsed time from my first request goes to my second request. I am sure it could have something to do with the glued together code, but I have tried everything I can think of. Any help would be appreciated.
<template>
<div class="row">
<div class="card col-md-6" v-for="(key, index) in keys" :key="index">
<div class="card-body">
<h5 class="card-title">Service Tag: {{ key.service_tag }}</h5>
<p class="card-text"> {{time}} {{key.reqTimestamp}}min</p>
<p class="invisible">{{ start(key.reqTimestamp) }}</p>
<p class="card-text">Associates Name: {{key.requestor_name}}</p>
<p class="card-text">Model: {{key.model}}</p>
<p class="card-text">Color: {{key.color}}</p>
<p class="card-text">Year: {{key.year}}</p>
<p class="card-text">Comments: {{key.comments}}</p>
<p class="card-text">Valet: {{key.valet}}</p>
<input class="form-control" v-model="key.valet" placeholder="Name of the person getting the car...">
<button
#click="claimedKey(key.id, key.valet)"
type="submit"
class="btn btn-primary"
>Claim</button>
<button v-if="key.valet !== 'Unclaimed'"
#click="unclaimedKey(key.id, key.valet)"
type="submit"
class="btn btn-primary"
>Unclaim</button>
<button class="btn btn-success" #click="complete(key.id)">Complete</button>
</div>
</div>
<!-- END OF CARD -->
<!-- START OF FORM -->
<div class="row justify-content-md-center request">
<div class="col-md-auto">
<h1 class="display-4">Operation Tiger Teeth</h1>
<form class="form-inline" #submit="newKey(service_tag, requestor_name, comments, model, year, color, valet, reqTimestamp)">
<div class="form-group col-md-6">
<label for="service_tag">Service Tag: </label>
<input class="form-control form-control-lg" v-model="service_tag" placeholder="ex: TB1234">
</div>
<div class="form-group col-md-6">
<label for="service_tag">Associates Name: </label>
<!-- <input class="form-control form-control-lg" v-model="requestor_name" placeholder="Your name goes here..."> -->
<div class="form-group">
<label for="exampleFormControlSelect1">Example select</label>
<select v-model="requestor_name" class="form-control" id="requestor_name">
<option>James Shiflett</option>
<option>Austin Hughes</option>
</select>
</div>
</div>
<div class="form-group col-md-6">
<label for="service_tag">Model: </label>
<input class="form-control form-control-lg" v-model="model" placeholder="What is the model of the vehicle?">
</div>
<div class="form-group col-md-6">
<label for="service_tag">Color: </label>
<input class="form-control form-control-lg" v-model="color" placeholder="What is the color of the vehicle?">
</div>
<div class="form-group col-md-6">
<label for="service_tag">Year: </label>
<input class="form-control form-control-lg" v-model="year" placeholder="What year is the car?">
</div>
<div class="form-group col-md-6">
<label for="service_tag">Comments: </label>
<input class="form-control form-control-lg" v-model="comments" placeholder="Place any additional comments here...">
</div>
<div class="form-group col-md-6 invisible">
<label for="service_tag">Valet: </label>
<input v-model="valet">
</div>
<div class="form-group col-md-6 invisible">
<label for="service_tag">Timestamp: </label>
<input v-model="reqTimestamp">
</div>
<div class="col-md-12">
<button class="btn btn-outline-primary" type="submit">Request A Key</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
import { db } from "../main";
import { setInterval } from 'timers';
export default {
name: "HelloWorld",
data() {
return {
keys: [],
reqTimestamp: this.newDate(),
service_tag: "",
requestor_name: "",
comments: "",
color: "",
model: "",
year: "",
inputValet: true,
valet: "Unclaimed",
state: "started",
startTime: '',
currentTime: Date.now(),
interval: null,
};
},
firestore() {
return {
keys: db.collection("keyRequests").where("completion", "==", "Incomplete")
};
},
methods: {
newKey(service_tag, requestor_name, comments, model, year, color, valet, reqTimestamp, completion) {
// <-- and here
db.collection("keyRequests").add({
service_tag,
requestor_name,
comments,
color,
model,
year,
valet,
reqTimestamp,
completion: "Incomplete",
});
this.service_tag = "";
this.requestor_name = "";
this.comments = "";
this.color = "";
this.model = "";
this.year = "";
this.reqTimestamp = this.newDate()
},
complete(id) {
db.collection("keyRequests").doc(id).update({
completion: "Complete"
})
},
// deleteKey(id) {
// db.collection("keyRequests")
// .doc(id)
// .delete();
claimedKey(id, valet) {
console.log(id);
this.inputValet = false
db.collection("keyRequests").doc(id).update({
valet: valet,
claimTimestamp: new Date()
})
},
moment: function () {
return moment();
},
newDate () {
var today = new Date()
return today
},
updateCurrentTime: function() {
if (this.$data.state == "started") {
this.currentTime = Date.now();
}
},
start(timestamp) {
return this.startTime = timestamp.seconds * 1000
}
},
mounted: function () {
this.interval = setInterval(this.updateCurrentTime, 1000);
},
destroyed: function() {
clearInterval(this.interval)
},
computed: {
time: function() {
return Math.floor((this.currentTime - this.startTime) /60000);
}
}
}
</script>
Ideally I am looking for the time lapse to follow each request.
So the problem lines in the template are:
<p class="card-text"> {{time}} {{key.reqTimestamp}}min</p>
<p class="invisible">{{ start(key.reqTimestamp) }}</p>
The call to start has side-effects, which is a major no-no for rendering a component. In this case it changes the value of startTime, which in turn causes time to change. I'm a little surprised this isn't triggering the infinite rendering recursion warning...
Instead we should just use the relevant data for the current iteration item, which you've called key. I'd introduce a method that calculates the elapsed time given a key:
methods: {
elapsedTime (key) {
const timestamp = key.reqTimestamp;
const startTime = timestamp.seconds * 1000;
return Math.floor((this.currentTime - startTime) / 60000);
}
}
You'll notice this combines aspects of the functions start and time. Importantly it doesn't modify anything on this.
Then you can call it from within your template:
<p class="card-text"> {{elapsedTime(key)}} {{key.reqTimestamp}}min</p>

Aurelia issue with setting element class based on obj.id === $parent.selectedId

I completed the contact-manager tut from Aurelia.io and am incorporating it into as task manager tut I'm putting together. The markup below sets the li class based on task.id === $parent.id.
task-list.html
<template>
<div class="task-list">
<ul class="list-group">
<li repeat.for="task of tasks" class="list-group-item ${task.id === $parent.selectedId ? 'active' : ''}">
<a route-href="route: tasks; params.bind: {id:task.id}" click.delegate="$parent.select(task)">
<h4 class="list-group-item-heading">${task.name}</h4>
<span class="list-group-item-text ">${task.due | dateFormat}</span>
<p class="list-group-item-text">${task.isCompleted}</p>
</a>
</li>
</ul>
</div>
task-list.js
#inject(WebAPI, EventAggregator)
export class TaskList {
constructor(api, ea) {
this.api = api;
this.tasks = [];
ea.subscribe(TaskViewed, x => this.select(x.task));
ea.subscribe(TaskUpdated, x => {
let id = x.task.id;
let task = this.tasks.find(x => x.id == id);
Object.assign(task, x.task);
});
}
created() {
this.api.getList().then( x => this.tasks = x);
}
select(task) {
this.selectedId = task.id;
return true;
}
}
If I edit the current task, represented by
task-detail.html
<template>
<require from="resources/attributes/DatePicker"></require>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Edit Task Profile</h3>
</div>
<div class="panel-body">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input type="text" placeholder="name" class="form-control" value.bind="task.name">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Description</label>
<div class="col-sm-10">
<input type="text" placeholder="description" class="form-control" value.bind="task.description">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Due Date</label>
<div class="col-sm-10">
<div class="input-group date">
<input type="text" datepicker class="form-control" value.bind="task.due | dateFormat:'L'"><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Urgency</label>
<div class="col-sm-10">
<input type="range" min="1" max="5" step="1" class="form-control" value.bind="task.urgency">
</div>
</div>
</form>
</div>
</div>
<div class="button-bar">
<button class="btn btn-info" click.delegate="addTask(task)" >Add New</button>
<button class="btn btn-success" click.delegate="save()" disabled.bind="!canSave">Save Edit</button>
</div>
</template>
task-detail.js
#inject(WebAPI, EventAggregator, Utils, DialogService)
export class TaskDetail {
constructor(api, ea, utils, dialogService) {
this.api = api;
this.ea = ea;
this.utils = utils;
this.dialogService = dialogService;
}
activate(params, routeConfig) {
this.routeConfig = routeConfig;
return this.api.getTaskDetails(params.id).then(task => {
this.task = task;
this.routeConfig.navModel.setTitle(task.name);
this.originalTask = this.utils.copyObj(task);
this.ea.publish(new TaskViewed(task));
});
}
get canSave() {
return this.task.name && !this.api.isRequesting;
}
save() {
console.log(this.task);
this.api.saveTask(this.task).then(task => {
this.task = task;
this.routeConfig.navModel.setTitle(task.name);
this.originalTask = this.utils.copyObj(task);
this.ea.publish(new TaskUpdated(this.task));
});
}
canDeactivate() {
if (!this.utils.objEq(this.originalTask, this.task)) {
let result = confirm('You have unsaved changes. Are you sure you wish to leave?');
if (!result) {
this.ea.publish(new TaskViewed(this.task));
}
return result;
}
return true;
}
addTask(task) {
var original = this.utils.copyObj(task);
this.dialogService.open({viewModel: AddTask, model: this.utils.copyObj(this.task)})
.then(result => {
if (result.wasCancelled) {
this.task.name = original.title;
this.task.description = original.description;
}
});
}
}
If a value has changed, navigation away from the current task is not allowed, and that works -- that is, the contact-detail part of the UI doesn't change. However, the task <li>, that one tries to navigate to still gets the active class applied. That's not supposed to happen.
If I step along in dev tools, on the Aurelia.io contact-manager, I see that the active class is briefly applied to the list item, then it goes away.
from the contact-manager's contact-list.js This was run when clicking an <li> and no prior item selected.
select(contact) {
this.selectedId = contact.id;
console.log(contact);
return true;
}
This logs
Object {__observers__: Object}
Object {id: 2, firstName: "Clive", lastName: "Lewis", email: "lewis#inklings.com", phoneNumber: "867-5309"}
The same code on my task-manager's (obviously with "contact" replaced by task") task-list.js logs
Object {description: "Meeting With The Bobs", urgency: "5", __observers__: Object}
Object {id: 2, name: "Meeting", description: "Meeting With The Bobs", due: "2016-09-27T22:30:00.000Z", isCompleted: false…}
My first instinct is to say it's got something to do with this.selectedId = contact.id
this there would refer to what I assume is a function called select (looks like the function keyword is missing in your example?) or the global object (i.e. window)
select(contact) {
this.selectedId = contact.id;
console.log(contact);
return true;
}
Fixed it. It wasn't working because I had pushstate enabled. That clears things up. Thanks therealklanni.

Categories