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.
Related
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 () {
...
}
Currently experimenting with JavaScript and building a To-Do List Web App. Having some issues with it. When I refresh the page, it gives the error:
TypeError: tasks is null
And when I press the submit button, it gives the error:
TypeError: cyclic object value
I was trying it out after reading a tutorial online on JavaScript and then I got the idea on implementing it with some basic project like this one.
How can I resolve the errors? I can't seem to figure out the solution.
JS Code:
document.getElementById('form-Task').addEventListener('submit', saveTask);
// Save new To-Do Item
function saveTask(e){
let title = document.getElementById('title').value;
let description = document.getElementById('description').value;
let task = {
title,
description
};
if(localStorage.getItem('tasks') === null) {
let = tasks = [];
tasks.push(tasks);
localStorage.setItem('tasks', JSON.stringify(tasks));
} else {
let tasks = JSON.parse(localStorage.getItem('tasks'));
tasks.push(task);
localStorage.setItem('tasks', JSON.stringify(tasks));
}
getTasks();
// Reset form-Task
document.getElementById('form-Task').reset();
e.preventDefault();
}
// Delete To-Do Item
function deleteTask(title) {
let tasks = JSON.parse(localStorage.getItem('tasks'));
for (let i = 0; i < tasks.length; i++){
if(tasks[i].title == title) {
tasks.splice(i, 1);
}
}
localStorage.setItem('tasks', JSON.stringify(tasks));
getTasks();
}
// Show To-Do List
function getTasks(){
let tasks = JSON.parse(localStorage.getItem('tasks'));
let tasksView = document.getElementById('tasks');
tasksView.innerHTML = '';
for(let i = 0; i < tasks.length; i++){
let title = tasks[i].title;
let description = tasks[i].description;
tasksView.innerHTML +=
`<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col-sm-3 text-left">
<p>${title}</p>
</div>
<div class="col-sm-7 text-left">
<p>${description}</p>
</div>
<div class="col-sm-2 text-right">
X
</div>
</div>
</div>
</div>`;
}
}
getTasks();
HTML code:
<nav class="navbar navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">My To-Do List</a>
</div>
</nav>
<div class="container">
<div class="row my-5">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<form id="form-Task">
<div class="form-group">
<input type="text" id="title" class="form-control" maxlength="50" autocomplete="off" placeholder="Title" required>
</div>
<div class="form-group">
<textarea type="text" id="description" cols="30" rows="10" class="form-control" maxlength="500" autocomplete="off" placeholder="Description" required></textarea>
</div>
<button type="submit" class="btn btn-success btn-block">Save</button>
</form>
</div>
</div>
</div>
<div class="col-md-8">
<div class="row">
<div class="col-sm-3 text-left">
<p class="font-weight-bold">Title</p>
</div>
<div class="col-sm-6 text-left">
<p class="font-weight-bold">Description</p>
</div>
<div class="col-sm-3 text-right">
<p class="font-weight-bold">Delete</p>
</div>
</div>
<hr>
<div id="tasks"></div>
</div>
</div>
</div>
<script src="js/app.js"></script>
I am still learning javascript so I might have missed something, but in your first if statement, you have an extra "=":
if(localStorage.getItem('tasks') === null) {
let = tasks = []; // let tasks = []
tasks.push(tasks);
and I am not sure about that first definition of the task variable. With curly braces, you make key-value pairs, but you didn't add the value. For example:
var person = { firstName: "John", lastName: "Doe" };
Hope I helped.
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>
I have a subscription feature which is open through checking a radio box. When open inside this section I have 2 radio buttons for subscribe the period weekly or monthly and below an item. When I pressing save the state of this period have to be saved for one item I have from the server. You can check the screenshot to see the view. Anyway, there is no save because the array result empty and that item is not in that array. My problem is I see the item below but somehow I'm not pushing it into the array with the period selected from radio buttons. I would like to receive help to understand why of that and what I should modify to make it works properly.
Please check the code I'm sharing controller and view:
searchApp.controller('UserSettingsCtrl', ['$scope', '$q', '$rootScope', 'aiStorage', 'userConfig', 'UserSettingsService', 'WebsiteSource', 'AnalyticsEmailService', 'toaster', '$translate', '$filter', 'ngTableParams',
function($scope, $q, $rootScope, store, userConfig, UserSettingsService, WebsiteSource, AnalyticsEmailService, toaster, $translate, $filter, ngTableParams) {
$scope.init = function() {
$scope.availableLanguages = {
da: 'Dansk',
en: 'English',
sv: 'Svensk'
}
window.scope = $scope
$scope.userInfo = store.get('user')
$scope.loadingAction = false
$scope.selectFlag = false
$scope.subscriptionEnginesFromServer = []
$scope.subscriptionEngines = []
$scope.analyticsEmailSettings = {}
$scope.engines = angular.copy(WebsiteSource.sites)
AnalyticsEmailService.getUserSubscription().then(
function success(response) {
$scope.loadingAction = false
$scope.subscription = response
console.log('response.data', response.data)
$scope.subscriptionEnginesFromServer = populateSubscribedEnginesFromServer(response.data)
getUnselectedEngines()
$scope.analyticsEmailSettings.subscribed = (response.data.length > 0)
},
function error() {})
}
function populateSubscribedEnginesFromServer(data) {
console.log('data', data)
var subscriptionEngines = []
for (var i = 0; i < data.length; i++) {
var subscription = data[i]
var engine = $scope.engines.filter(function(x) {
if (x.id === subscription.engine) {
var index = $scope.engines.indexOf(x)
$scope.engines[index].type = subscription.type
}
return x.id === subscription.engine
})[0]
console.log('engine', engine)
if (engine) subscription.name = engine.name
subscriptionEngines.push(subscription)
}
console.log('subscriptionEngines', subscriptionEngines)
if (subscriptionEngines.length == 0) {
$scope.analyticsEmailSettings.subscription = 'WeeklyAnalytics'
} else {
$scope.analyticsEmailSettings.subscription = subscriptionEngines[0].type
}
return subscriptionEngines
}
// Save for all always the user have to press the save button if wants save no auto save as it is now
$scope.save = function() {
$scope.loadingAction = true
if ($scope.analyticsEmailSettings.subscribed) {
AnalyticsEmailService.updatesubscriptions($scope.subscriptionEnginesFromServer, function success(response) {}, function error() {})
} else {
$scope.analyticsEmailSettings.subscription = 'WeeklyAnalytics'
$scope.subscriptionEnginesFromServer = []
AnalyticsEmailService.updatesubscriptions($scope.subscriptionEnginesFromServer, function success(response) {}, function error() {})
}
UserSettingsService.save({
userId: $scope.userInfo.id
}, $scope.userInfo, function() {
$scope.loadingAction = false
userConfig.setCurrentUserConfig($scope.userInfo)
userConfig.setUserLocale()
store.set('user', $scope.userInfo)
toaster.pop({
type: 'success',
body: $translate.instant('notifications_user_settings_changed_success')
})
}, function() {})
$scope.subscriptionEngines = []
}
// removeSelectedEngines
getUnselectedEngines = function() {
for (var i = 0; i < $scope.engines.length; i++) {
if ($scope.subscriptionEnginesFromServer.filter(function(x) {
return x.engine === $scope.engines[i].id
}).length == 0)
$scope.engines[i].type = ''
}
}
// #todo: consider referring by array key instead of engineId
function updatesubscriptions(engineId, subscriptionType) {
var engine
for (var i = 0; i < $scope.subscriptionEnginesFromServer.length; i++) {
if ($scope.subscriptionEnginesFromServer[i].engine == engineId) {
engine = $scope.subscriptionEnginesFromServer[i]
}
}
engine.type = subscriptionType
engine.engine = engineId
}
$scope.updateSubscriptionType = function(engine) {
for (var i = 0; i < $scope.subscriptionEnginesFromServer.length; i++) {
updatesubscriptions($scope.subscriptionEnginesFromServer[i].engine, $scope.analyticsEmailSettings.subscription)
}
}
$scope.addSubscribedEngine = function(engine) {
$scope.subscriptionEngines = []
engine.type = $scope.analyticsEmailSettings.subscription
$scope.subscriptionEnginesFromServer.push({
type: engine.type,
engine: engine.id,
name: engine.name
})
}
$scope.selectFirstUnsubscribedEngine = function() {
var filtered
filtered = $scope.engines.filter(function(x) {
return x.type == ''
})
filtered = $filter('orderBy')(filtered, 'name')
$scope.engine.current = filtered.length ? filtered[0] : null
}
$scope.removeSubscribedEngine = function(engine) {
engine.type = ''
for (var i = 0; i < $scope.subscriptionEnginesFromServer.length; i++) {
if ($scope.subscriptionEnginesFromServer[i].engine == engine.id) {
$scope.subscriptionEnginesFromServer.splice(i, 1)
}
}
save()
}
}])
View:
<div ng-controller="UserSettingsCtrl" ng-init="init()">
<div class="content">
<header class="flex-container row header">
<div class="flex-1">
<h1 class="flex-1">{{ 'user_settings_title' | translate }}</h1>
</div>
<!--<a class="logout" href ui-sref="account.settings.changepassword">{{ 'user_change_password_menu' | translate }}</a>-->
</header>
<div class="main-edit">
<div class="subsection">
<div class="inputs-container-row full-width">
<div class="input-group full-width">
<div class="inputfield">
<label class="label ng-binding" for="name">
{{ 'user_settings_firstname_label' | translate }}
</label>
<input type="text" name="firstname" ng-model="userInfo.firstName" class="flex-1" ng-class="{'first-letter-to-upper' : userInfo.firstName.length > 0 }" placeholder="{{ 'user_settings_firstname_placeholder' | translate }}">
</div>
</div>
<div class="input-group full-width">
<div class="inputfield">
<label class="label ng-binding" for="name">
{{ 'user_settings_lastname_label' | translate }}
</label>
<input type="text" name="lastname" ng-model="userInfo.lastName" class="flex-1" ng-class="{'first-letter-to-upper' : userInfo.lastName.length > 0 }" placeholder="{{ 'user_settings_lastname_placeholder' | translate }}">
</div>
</div>
</div>
<div class="inputs-container-row full-width">
<div class="inputs-container-row half-width">
<div class="input-group full-width">
<label class="label" for="name">{{ 'user_settings_language_label' | translate }}</label>
<div class="select-group full-width">
<select class="select" id="selectLanguage" ng-model="userInfo.language" ng-options="key as value for (key , value) in availableLanguages"></select>
<label for="selectLanguage"><span class="fa fa-angle-down"></span></label>
</div>
</div>
</div>
<div class="inputs-container-row half-width">
<div class="input-group full-width">
<label class="label" for="name">
{{ 'user_settings_phone_label' | translate }}
</label>
<input type="text" name="lastname" ng-model="userInfo.phoneNumber" placeholder="{{ 'user_settings_phone_placeholder' | translate }}">
</div>
</div>
</div>
</div>
<div class="subsection">
<div class="inputs-container-row half-width">
<div class="input-group full-width">
<label class="label" for="name">
{{ 'user_settings_password_label' | translate }}
<a ui-sref="account.settings.changepassword" class="button button-link--primary button--first">
{{ 'user_settings_password_button' | translate }}...
</a>
</label>
</div>
</div>
</div>
</div>
<div class="flex-container row header">
<div class="flex-1">
<h1 class="flex-1">{{ 'user_settings_emailStatistics_title' | translate }}</h1>
</div>
</div>
<!--||| Subscribe Start |||-->
<div class="main-edit">
<div class="subsection">
<div class="flex-container row">
<div class="radiobutton-group">
<div class="width-140">
<input id="subscribed" type="checkbox" ng-model="analyticsEmailSettings.subscribed" value="subscribed" class="radiobutton">
<label class="label highlight inline no-bottom-margin" for="subscribed">
{{ 'user_settings_emailStatistics_subscribe' | translate }}
</label>
</div>
</div>
</div>
<div ng-show="analyticsEmailSettings.subscribed">
<div class="flex-container row">
<div class="input-group flex-1" ng-switch="analyticsEmailSettings.subscription">
<label class="label" for="name">{{ 'user_settings_emailStatistics_recurrence' | translate }}</label>
<div class="inputs-container-row half-width" name="oftenReportSent">
<span class="radiobutton flex-1" ng-class="{'checked' : analyticsEmailSettings.subscription === 'WeeklyAnalytics'}" name="radio">
<input type="radio" name="WeeklyAnalytics" ng-model="analyticsEmailSettings.subscription" ng-change="updateSubscriptionType()" ng-checked="analyticsEmailSettings.subscription === 'WeeklyAnalytics'" value="WeeklyAnalytics" id="WeeklyAnalytics" ng-required="">
<label for="WeeklyAnalytics">{{ 'user_settings_emailStatistics_weekly' | translate }}</label>
</span>
<span class="radiobutton flex-1" ng-class="{'checked' : analyticsEmailSettings.subscription === 'MonthlyAnalytics'}">
<input type="radio" name="MonthlyAnalytics" ng-model="analyticsEmailSettings.subscription" ng-change="updateSubscriptionType()" ng-checked="analyticsEmailSettings.subscription === 'MonthlyAnalytics'" value="MonthlyAnalytics" id="MonthlyAnalytics" ng-required="">
<label for="MonthlyAnalytics">{{ 'user_settings_emailStatistics_monthly' | translate }}</label>
</span>
</div>
<div> <span style="color:red;" ng-show="analyticsEmailSettings.subscription == null">Please select option</span></div>
</div>
</div>
<h1>Before</h1>
<div ng-if="engines.length == 1">
<ul class="tags tags--inline item-with-inline-buttons">
<li ng-repeat="engine in engines | orderBy:'name'">
{{engine.name}}
<span class="button-icon button--primary button--delete" ng-click="removeSubscribedEngine(engine); selectFirstUnsubscribedEngine()">
<i class="fa fa-trash-o"></i>
</span>
</li>
</ul>
</div>
<h1>after</h1>
<div ng-show="engines.length > 1">
<div class="flex-container row" ng-show="((engines | filter:{type:''}:true).length != 0)">
<div class="input-group full-width">
<label class="label" for="selectEngine">
{{ 'user_settings_emailStatistics_engines_label' | translate }}:
</label>
<div class="half-width inputfield--horizontal" style="margin-bottom: 10px;">
<div class="full-width select-group" ng-if="(engines | filter:{type:''}:true).length > 0">
<select class="select" id="selectEngine" ng-model="$parent.engine.current" ng-options="website.name for website in engines | filter:{type:''}:true | orderBy:'name'" ng-init="$parent.engine.current = (engines | filter:{type:''}:true | orderBy:'name')[0]">
</select>
<label for="selectSubscription"><span class="fa fa-angle-down"></span></label>
</div>
<span ng-show="engines.length == 1">{{(engines | filter:{type:''}:true)[0].name}}</span>
<div id="btnAddWebSitesSubscription" ng-show="engines.length > 0" class="button button--add" ng-click="addSubscribedEngine(engine.current); selectFirstUnsubscribedEngine()"><i class="fa fa-plus"></i></div>
</div>
</div>
</div>
<div ng-model="successMessage" ng-show="showMessage" style="color:green;" class="message fadein fadeout">{{successMessage}}</div>
</div>
<h1 ng-show="subscriptionEnginesFromServer.length > 0 && engines.length > 1">Websites Subscribed</h1>
<div class="flex-container row" ng-if="subscriptionEnginesFromServer.length > 0 && engines.length > 1">
<ul class="tags tags--inline item-with-inline-buttons">
<li ng-repeat="engine in engines | filter:{type:'Analytics'} | orderBy:'name'">
{{engine.name}}
<span class="button-icon button--primary button--delete" ng-click="removeSubscribedEngine(engine); selectFirstUnsubscribedEngine()">
<i class="fa fa-trash-o"></i>
</span>
</li>
</ul>
</div>
</div>
</div>
<footer class="flex-container flex-end row footer">
<button class="button button--primary button--action" ng-click="save();">
<i ng-show="loadingAction" class="fa fa-spinner fa-spinner-custom"></i>
<span ng-show="!loadingAction">{{ 'general_save' | translate }}</span>
</button>
</footer>
</div>
</div>
Be careful of using ng-hide/ng-show as when the partial is 'hidden' it destroys the model (if there are any) contained within itself (so, use ng-if instead).
In your html in the subscription section, you make a call like this:
ng-change="updateSubscriptionType()"
But in your javascript you have:
$scope.updateSubscriptionType = function(engine) {
for (var i = 0; i < $scope.subscriptionEnginesFromServer.length; i++) {
updatesubscriptions($scope.subscriptionEnginesFromServer[i].engine, $scope.analyticsEmailSettings.subscription)
}
}
So it's expecting an 'engine' argument, which you never pass in. But looking at the code you don't use the engine argument anyway; you use the 'engine' property of $scope.subscriptionEnginesFromServer[i] but that's it.
It however, doesn't do anything that I can see, anyway. It loops through an empty array then calls updatesubscriptions() to do something, but it won't actually call it.
Also, the updatesubscriptions() method itself doesn't actually do anything. This is probably why you're not getting anything in your array. I'd suggest modifying your template slightly, because the subscription radio buttons are outside the engines loop, so you won't be able to associate the subscription type with any engine. Once you have done that, then the subscriptions type radio buttons will have access to 'engine', which you can pass in. Modify your method accordingly:
$scope.updateSubscriptionType = function(engine) {
if (!$scope.subscriptionEnginesFromService.includes(engine)) {
$scope.subscriptionEnginesFromService.push(engine);
}
updatesubscriptions(engine, $scope.analyticsEmailSettings.subscription);
}
And also modify the updatesubscriptions() slightly.
I'm trying to write a custom form control in Vue.js to learn how to package together multiple form inputs into a single custom component.
My project setup looks like this (standard webpack-simple vue cli setup):
$ tree -L 1
.
├── README.md
├── index.html
├── node_modules
├── package.json
├── src
└── webpack.config.js
Here's my top level Vue instance .vue file:
// App.vue
<template>
<div class="container">
<form v-if="!submitted" >
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<form>
<fullname v-model="user.fullName"></fullname>
<div class="form-group">
<label for="email">Email:</label>
<input id="email" type="email" class="form-control" v-model="user.email">
<label for="password">Password:</label>
<input id="password" type="password" class="form-control" v-model="user.password">
</div>
<fieldset class="form-group">
<legend>Store data?</legend>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="storeDataRadios" id="storeDataRadios1" value="true" checked v-model="user.storeData">
Store Data
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="storeDataRadios" id="storeDataRadios2" value="false" v-model="user.storeData">
No, do not make my data easily accessible
</label>
</div>
</fieldset>
</form>
<button class="btn btn-primary" #click.prevent="submitForm()">Submit</button>
</div>
</div>
</form>
<hr>
<div v-if="submitted" class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<h4>Your Data</h4>
</div>
<div class="panel-body">
<p>Full Name: {{ user.fullName }}</p>
<p>Mail: {{ user.email }}</p>
<p>Password: {{ user.password }} </p>
<p>Store in Database?: {{ user.storeData }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import FullName from './FullName.vue';
export default {
data() {
return {
user: {
fullName: 'John Smith',
email: '',
password: '',
storeData: true,
},
submitted: false
}
},
methods: {
submitForm() {
this.submitted = true;
},
},
components: {
'fullname' : FullName,
}
}
</script>
<style>
</style>
And here's the custom component file:
<template>
<div class="form-group">
<label for="firstName">First name:</label>
<input id="firstName" type="text" class="form-control" :value="first" #input="emitChange(true, $event)">
<label for="lastName">Last name:</label>
<input id="lastName" type="text" class="form-control" :value="last" #input="emitChange(false, $event)">
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
emitChange(isFirst, evt) {
let name = '';
let evtValue = evt.target.value == undefined ? "" : evt.target.value;
if (isFirst) {
name = evtValue +" "+ this.second;
} else {
name = this.first +" "+ evtValue;
}
this.value = name;
this.$emit('input', this.value);
}
},
computed: {
first() {
if (this.value != "")
return this.value.split(" ")[0];
else return "";
},
last() {
if (this.value != "")
return this.value.split(" ")[1];
else return "";
}
}
}
</script>
Which I also realize I'm messing up because I'm directly editing a prop value with:
this.value = name;
(not an error, but Vue.JS gives a warning).
However, even before that, typing in the first input box causes the second input box to update its value to undefined (...wat!?).
Would be grateful for advice on how to properly set up custom form control components! (and why this example isn't working).
I think, the problem here is you never know where the first name ends and where the last name starts. Take Barack Hussein Obama for instance and imagine he's Belgian, his name would be Barack Hussein van Obama. You can't safely assume which part ist first and which part is lastname.
However, if you could say the firstname is exactly one word and the rest is lastname, here's an example implementation (stripped down). To illustrate the problem, try to put in Obamas second name.
Otherwise, the component behaves like a two way bound component. You can alter the separate values on the fullname component, or edit the fullname on the root component and everything stays up to date. The watcher listens for changes from above, the update method emits changes back up.
Vue.component('fullname', {
template: '#fullname',
data() {
// Keep the separate names on the component
return {
firstname: this.value.split(' ')[0],
lastname: this.value.split(' ')[1],
}
},
props: ['value'],
methods: {
update() {
// Notify the parent of a change, chain together the name
// and emit
this.$emit('input', `${this.firstname} ${this.lastname}`);
},
},
mounted() {
// Parse the prop input and take the first word as firstname,
// the rest as lastname.
// The watcher ensures that the component stays up to date
// if the parent changes.
this.$watch('value', function (value){
var splitted = value.split(' ');
this.firstname = splitted[0];
splitted.shift();
this.lastname = splitted.join(' ');
});
}
});
new Vue({
el: '#app',
data: {
fullname: 'Barack Obama',
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<fullname v-model="fullname"></fullname>
<p>Thanks, {{fullname}}</p>
<input v-model="fullname" />
</div>
<template id="fullname">
<div>
<input v-model="firstname" #input="update" type="text" id="firstname" />
<input v-model="lastname" #input="update" type="text" id="lastname" />
</div>
</template>