vuejs Data property undefined - javascript

I am having trouble where a property is returning undefined inside the vuejs instance, but returns the correct value in the HTML.
data: {
...
userFilter: 'all',
...
},
The alert is returning this.userFilter as undefined
filters: {
all: function(tasks) {
alert(this.userFilter); // This is undefined
if(this.userFilter == 'all'){
return tasks;
}else{
return tasks.filter(function(task){
return task.user_id == this.userFilter;
});
}
},
}
Dropdown to select user to filter by
<select class="form-control" v-if="visibility == 'all'" v-model="userFilter">
<option selected value="all">Showing all users tasks</option>
<option v-for="user in users"
value="#{{user.id}}">
#{{user.first_name}} #{{user.last_name}}
</option>
</select>
The below correctly displays the value of userFilter
#{{ userFilter }}
Entire code:
var tasks = new Vue({
el: '#tasks',
data: {
tasks: [],
users: [],
newTask: { description: '', due_at: '', user_id: '', completed: false },
editingTask: false,
showAlert: false,
loading: true,
visibility: 'active',
validation: [],
showUser: null,
authUser: {}
},
ready: function() {
this.getAuthUser();
this.getTasks();
this.getUsers();
},
computed: {
filteredTasks: function () {
return this.$options.filters[this.visibility](this.tasks);
},
remaining: function() {
return this.tasks.filter(function(task){
return !task.completed && task.user_id == this.authUser.id;
}).length;
}
},
filters: {
all: function(tasks) {
return tasks;
},
active: function(tasks) {
return tasks.filter(function(task){
return !task.completed;
});
},
completed: function(tasks) {
return tasks.filter(function(task){
return task.completed;
});
},
groupByDate: function(tasks) {
var result = {};
for (var i = 0; i < tasks.length; i++) {
var task = tasks[i];
// Convert due_at date to be used as array key
var due_at = moment(task.due_at,'YYYY-MM-DD').format('DD-MM-YYYY');
if (result[due_at]) {
result[due_at].push(task);
} else {
result[due_at] = [task];
}
}
return result;
},
newDate: function(date) {
return moment(date, 'DD-MM-YYYY').format('LL');
},
usersFilter: function(tasks, user_id) {
if(this.visibility == 'all' && user_id){
return tasks.filter(function(task){
return task.user_id == user_id;
});
}else{
return tasks;
}
}
},
methods: {
getTasks: function () {
this.loading = true;
this.$http.get('api/tasks/'+ this.visibility).success(function(tasks) {
this.$set('tasks', tasks);
this.loading = false;
}).error(function(error) {
console.log(error);
});
},
getUsers: function() {
this.$http.get('api/users/all',function(users){
this.$set('users',users);
});
},
getAuthUser: function() {
this.$http.get('api/users/current-user').success(function(authUser) {
this.$set('authUser',authUser);
});
},
toggleVisibility: function(filter) {
this.visibility = filter;
this.getTasks();
},
open: function(e) {
e.preventDefault();
$('#add-task-modal').slideDown();
},
close: function(e) {
e.preventDefault();
$('#add-task-modal').slideUp();
},
toggleAlert: function(message) {
this.showAlert = true;
$('.alert').text(message);
$('.alert').fadeIn().delay(1000).fadeOut();
this.showAlert = false;
},
addTask: function(e) {
e.preventDefault();
if ( ! this.newTask) return;
var task = this.newTask;
this.$http.post('api/tasks', task)
.success(function(data){
task.id = data.task_id;
task.due_at = moment(task.due_at,'DD-MM-YYYY').format('YYYY-MM-DD');
if(this.visibility == 'all'){
this.tasks.push(task);
}else if(this.authUser.id == task.user_id){
this.tasks.push(task);
}
$('.datepicker').datepicker('clearDates');
this.validation = [];
this.newTask = { description: '', due_at: '', user_id: '', completed: '' };
this.$options.methods.toggleAlert('Task was added.');
})
.error(function(validation){
this.$set('validation', validation);
});
},
toggleTaskCompletion: function(task) {
task.completed = ! task.completed;
this.$http.post('api/tasks/complete-task/'+ task.id, task);
},
editTask: function(task) {
if(task.completed) return;
this.editingTask = task;
},
cancelEdit: function (todo) {
this.editingTask = false;
},
updateTask: function(task) {
task.description = task.description.trim();
this.$http.patch('api/tasks/'+ task.id, task);
this.$options.methods.toggleAlert('Task was updated.');
return this.editingTask = false;
},
deleteTask: function(due_at,task) {
if(confirm('Are you sure you want to remove this task?')){
this.tasks.$remove(task);
this.$http.delete('api/tasks/'+ task.id, task);
this.$options.methods.toggleAlert('Task was removed.');
}
}
},
directives: {
'task-focus': function (value) {
if (!value) {
return;
}
var el = this.el;
Vue.nextTick(function () {
el.focus();
});
}
}
})

Try to use tasks.$data.visibility.

Related

Vue.js How to clear the selected option in a dropdown?

I added a button inside my dropdown that needs to clear the selected city.
I added an event but it isn't clearing the selected option.
Could you please suggest me what am I doing wrong ? Thank you very much.
This is the button in my dropdown
methods: {
...mapActions({
fetchCitiesByName: "fetchCitiesByName",
fetchCityDetails: "fetchCityDetails",
}),
async onClientComboSelect({value, label})
{
this.cityId = value;
this.city = label;
this.option.city = label;
this.additionalSearchField = {cityId: this.option.cityId, label: this.option.city};
await this.fetchCityInfo({id: this.option.cityId, label: this.option.city});
},
noCitySelected()
{
this.option.cityId = null;
this.$emit('input', this.option.cityId);
this.$emit('on-button-action', item);
},
<!-- Select City -->
<div
class="select-form-field"
>
<label
for="city"
class="inline-3-columns"
>
<span class="title__field">City*</span>
<combo-select
id="city"
v-model="option.cityId"
api-location="fetchCitiesByName"
api-details-location="fetchCityDetails"
search-parameter="cityName"
:additional-search-fields="additionalSearchField"
:transformer="cityTransformer"
:button="{event: noCitySelected, text: 'No City', icon: 'minus'}"
:config="{
...comboConfig,
searchLabel: 'Search Cities',
}"
:button-event="noCitySelected"
class="input input__typeahead"
#on-select-item="onCityComboSelect"
/>
<input
v-model="option.cityId"
type="hidden"
name="cityId"
>
</label>
</div>
<!-- End -->
here is the dropdown combo-select that I need I need to use. Is it possible to clear
<script>
const COMBO_STATES = Object.freeze({
OPEN: "OPEN",
CLOSED: "CLOSED"
});
const LOADING_STATES = Object.freeze({
LOADING: "LOADING",
BLOCKED: "BLOCKED",
DEFAULT: "DEFAULT"
});
export default {
directives: {
clickOutside: vClickOutside.directive,
focus: {
inserted: (el) =>
{
el.focus();
}
}
},
components:
{
InfiniteScroll
},
model: {
prop: 'selectedId',
},
props:
{
apiLocation: {
type: String,
required: true,
default: ""
},
apiDetailsLocation: {
type: String,
required: false,
default: ""
},
transformer: {
type: Function,
required: true,
default: () => ([])
},
selectedId: {
type: Number|null,
required: false,
default: null
},
selectedItems: {
type: Array,
required: false,
default: () => ([])
},
searchParameter: {
type: String,
required: false,
default: "name"
},
// temporary as the form css is too much hassle to adjust
details: {
type: String|null,
required: false,
default: null
},
additionalSearchFields: {
type: Object,
required: false,
default: () => ({})
},
getter: {
type: String,
required: false,
default: ""
},
button: {
type: Object,
required: false,
default: null
},
config: {
type: Object,
required: true,
default: () => ({})
},
canSendDifferentValue: {
type: Boolean,
required: false,
default: true
}
},
data()
{
return {
searchable: "",
openState: COMBO_STATES.CLOSED,
itemsInitializationNotEmpty: false,
selectedItem: CSItem(),
items: [],
iterations: 0,
isLoading: false,
page: 0,
pagingLoadingState: LOADING_STATES.DEFAULT,
defaultConfig: {
itemsPerPage: 20,
numberOfItemsShown: 4,
searchLabel: "Search for more...",
showDefaultLabelOnSelect: false,
clearSelectedItems: false,
isEditable: true,
isImmediate: true
}
};
},
computed:
{
hasSubitemSlot()
{
return !!this.$slots.subitem || !!this.$scopedSlots.subitem;
},
isComboSelectEditable()
{
return this.innerConfig.isEditable;
},
isOpen()
{
return this.openState === COMBO_STATES.OPEN;
},
comboItems()
{
let items = this.items;
if(this.innerConfig.clearSelectedItems)
items = this.items.filter(({id}) => !this.selectedItems.includes(id));
return CSItemList(items, this.transformer);
},
comboSelectItem()
{
const defaultLabel = this.innerConfig.isEditable ? "Select" : "";
if(this.innerConfig.showDefaultLabelOnSelect) return defaultLabel;
if(this.selectedItem.value)
{
const {label = defaultLabel} = this.comboItems.find(({value}) => value === this.selectedItem.value) || {};
return label;
}
return this.selectedItem.label ? this.selectedItem.label : defaultLabel;
},
innerConfig()
{
return Object.assign({}, this.defaultConfig, this.config);
},
hasNoItems()
{
return this.filterItems(this.items).length === 0;
},
skip()
{
return this.innerConfig.itemsPerPage * this.page;
},
isPagingLoading()
{
return this.pagingLoadingState === LOADING_STATES.LOADING;
},
isPagingLoadingBlocked()
{
return this.pagingLoadingState === LOADING_STATES.BLOCKED;
}
},
watch:
{
additionalSearchFields:
{
deep: true,
handler(newValue, oldValue)
{
if(newValue && !isEqual(newValue, oldValue))
this.getItems(false);
}
},
selectedId: function(newValue, oldValue)
{
if(newValue === oldValue) return;
this.findSelectedItem();
},
items: function(newValue, oldValue)
{
const allItems = this.filterItems(newValue);
if (allItems.length === 0 && oldValue.length !== 0) return;
if (allItems.length === 0 && oldValue.length === 0 && this.itemsInitializationNotEmpty) return;
if(allItems.length === 0)
this.$emit("on-no-items");
this.itemsInitializationNotEmpty = true;
}
},
async mounted()
{
try
{
if(!this.innerConfig.isImmediate) return;
const initialSearchParams = this.searchable.length > 0 ? {[this.searchParameter]: this.searchable} : {};
this.items = await this.$store.dispatch(this.apiLocation, Object.assign({
top: this.innerConfig.itemsPerPage,
load: false,
skip: this.page,
...initialSearchParams
}, this.additionalSearchFields
));
this.findSelectedItem();
this.searchValue$ = "";
this.requestSubscription = requestSourceService
.getInstance()
.search
.subscribe(search =>
{
const {source, value} = search;
if(this.searchValue$ === value)
Reflect.apply(source.cancel, null, [
"Cancel previous request"
]);
this.searchValue$ = value;
});
}
catch(error)
{
this.errorHandler();
}
},
destroyed()
{
if(this.requestSubscription)
this.requestSubscription.unsubscribe();
},
methods:
{
errorHandler()
{
this.isLoading = false;
this.items = [];
},
search: debounce(async function()
{
if(this.searchable.length > 0 && this.searchable.length < 2) return;
this.isLoading = true;
await this.getItems(false);
}, 300),
async getItems(isCancelable = true)
{
try
{
this.page = 0;
this.items = await this.$store.dispatch(this.apiLocation, Object.assign({
top: this.innerConfig.itemsPerPage,
load: false,
skip: this.skip,
[this.searchParameter]: this.searchable ? this.searchable : null,
cancelable: isCancelable,
isThrowable: true,
}, this.additionalSearchFields));
this.isLoading = false;
const allItems = this.filterItems(this.items);
if(allItems.length === 0)
this.$emit("on-no-items");
this.pagingLoadingState = LOADING_STATES.DEFAULT;
this.findSelectedItem();
}
catch(error)
{
this.errorHandler(error);
}
},
async findSelectedItem()
{
if(!this.selectedId) return;
const item = this.comboItems.find(item => item.value === this.selectedId);
if(item)
{
const selectedItem = CSItem({
value: this.selectedId,
label: item ? item.label : null,
...item
});
this.selectedItem = selectedItem;
this.iterations = 0;
}
else
{
{
if(!this.apiDetailsLocation) return;
if(this.iterations === 1) return;
const itemDetails = await this.$store.dispatch(this.apiDetailsLocation, {
id: this.selectedId,
isThrowable: true
});
this.items.push(itemDetails);
this.iterations = 1;
await this.findSelectedItem();
}
catch (error)
{
console.error(error);
}
}
},
selectItem(item)
{
this.selectedItem = item;
// check if it should be sent
if(this.canSendDifferentValue)
this.$emit('input', item.value);
this.$emit('on-select-item', item);
this.close();
},
async onScrollEnd()
{
if(this.isPagingLoading || this.isPagingLoadingBlocked || (this.searchable.length > 0 && this.searchable.length < 2)) return;
try
{
this.pagingLoadingState = LOADING_STATES.LOADING;
this.page++;
const items = await this.$store.dispatch(this.apiLocation, Object.assign({
top: this.innerConfig.itemsPerPage,
load: false,
skip: this.skip,
[this.searchParameter]: this.searchable ? this.searchable : null,
isThrowable: true,
}, this.additionalSearchFields));
if(items.length === 0)
{
this.pagingLoadingState = LOADING_STATES.BLOCKED;
return;
}
this.items = this.items.concat(items);
const allItems = this.filterItems(this.items);
if(allItems.length === 0)
{
this.$emit("on-no-items");
}
this.pagingLoadingState = LOADING_STATES.DEFAULT;
}
catch(error)
{
console.error(error);
this.errorHandler(error);
this.pagingLoadingState = LOADING_STATES.DEFAULT;
}
},
filterItems(items)
{
return this.innerConfig.itemsFilter ? this.innerConfig.itemsFilter(items) : items;
},
dispatch(action)
{
this.$emit("on-button-action", action);
},
toggleComboOpenState()
{
if(!this.innerConfig.isEditable) return;
return this.openState = this.isOpen ? COMBO_STATES.CLOSED : COMBO_STATES.OPEN;
},
close()
{
this.openState = COMBO_STATES.CLOSED;
}
}
};
</script>
<template>
<div>
<div
v-click-outside="close"
:class="['combo-select', { 'combo-select__disabled': !isComboSelectEditable }]"
#click="toggleComboOpenState"
>
<span class="combo-select__selecteditem">
<span
v-if="comboSelectItem === 'Select'"
id="selected-item"
>{{ comboSelectItem }}</span>
<span
v-else
id="selected-item"
v-tippy="{ placement : 'bottom', content: comboSelectItem, }"
>{{ comboSelectItem }}</span>
</span>
<font-awesome-icon
icon="caret-down"
class="dropdown--arrow f-22"
/>
<transition
name="slidedown"
appear
>
<div
v-if="isOpen"
class="sub-menu"
>
<section class="sub-search input input__typeahead field">
<div class="input-group">
<input
v-model="searchable"
v-focus
type="text"
:placeholder="innerConfig.searchLabel"
#click.stop=""
#input="search"
>
<div class="input-group-append">
<font-awesome-icon
v-if="!isLoading"
icon="search"
class="typeahead-icon"
/>
<font-awesome-icon
v-if="isLoading"
icon="spinner"
class="fa-spin relative f-25 cl-body"
/>
</div>
</div>
</section>
<infinite-scroll
v-if="!hasSubitemSlot"
:button="button"
:is-loading="isPagingLoading"
#scroll-end="onScrollEnd"
>
<template #list>
<h2
v-for="(item, index) in comboItems"
:key="`${item.label}-${index}`"
v-tippy="{
placement : 'bottom',
content: item.label,
}"
:class="['sub-menu__item', {'selected': selectedItem.value === item.value}]"
#click.stop="selectItem(item)"
>
{{ item.label }}
</h2>
<h2
v-if="hasNoItems"
class="sub-menu__item pointer-events-none"
>
No items
</h2>
</template>
</infinite-scroll>
<infinite-scroll
v-if="hasSubitemSlot"
:button="button"
:is-loading="isPagingLoading"
#scroll-end="onScrollEnd"
>
<template #list>
<div
v-for="(item, index) in comboItems"
:key="`${item.label}-${index}`"
>
<slot
name="subitem"
:index="index"
:item="item"
:isSelected="selectedItem.value === item.value"
:close="close"
:action="selectItem"
/>
<h2
v-if="hasNoItems"
class="sub-menu__item pointer-events-none"
>
No items
</h2>
</div>
</template>
</infinite-scroll>
<section
v-if="button"
class="sub-button"
>
<button
class="btn btn--creation btn--creation--grey btn--creation--square w-100 h-100 br-r-0"
#click="dispatch(button.action)"
>
<font-awesome-icon :icon="button.icon" />
<span>{{ button.text }}</span>
</button>
</section>
<!-- this should be shown only on infinite loading -->
</div>
</transition>
</div>
<span
v-if="details"
class="flex w-mc f-11 cl-6f-grey p-l-10 p-t-3"
>({{ details }})</span>
</div>
</template>
You should show your UI component library name, because combo-select would have its own usage.
Maybe you can install vue-devtools to inspect data or other bugs in your development environment.

How to apply a settimeout in the VUEJS script?

I am developing my first application in vuejs and in the initial data upload in the script I need to modify the data I received from a call to the database.
Since I have modified the data it returns an error in the initial load of the page and after a few seconds it loads without problem.
I am trying to wrap this function in a settimeout but it returns an error in vuejs.
How can I apply this setTimeout?
here my script
<script>
export default {
data () {
return {
step: 1,
selected: 1
}
},
components: {
},
computed:{
selectedBasket() {
return !this.$store.getters.basket ? null : this.$store.getters.basket
},
items(){
return !this.$store.getters.items ? null : this.$store.getters.items
},
setTimeout(() => {
filteredEstimation(){
this.$store.getters.estimations.map(function(estimation) {
estimation.offers.map(function(offer) {
offer.name = offer.name.split(" ").reverse().slice(1).reverse().join(" ");
if (offer.name.includes("first")) {
offer.description = "first option";
}
if (offer.name.includes("second")) {
offer.description = "second option";
}
if (offer.name.includes("third")) {
offer.description = "third option";
}
});
});
return !this.$store.getters.estimations ? null : this.$store.getters.estimations.filter( item => item.id == this.selected )[0].offers
}, 700);
},
methods: {
getItemsName(item) {
if(item == 1){
return 'bag'
} else if(item == 2){
return 'paper'
} else {
return 'pen'
}
}
}
}
</script>
You're using that function inside the computed option, that's not allowed, you should define it in the mounted hook like :
<script>
export default {
data () {
return {
step: 1,
selected: 1
}
},
components: {
},
computed:{
selectedBasket() {
return !this.$store.getters.basket ? null : this.$store.getters.basket
},
items(){
return !this.$store.getters.items ? null : this.$store.getters.items
},
},
methods: {
getItemsName(item) {
if(item == 1){
return 'bag'
} else if(item == 2){
return 'paper'
} else {
return 'pen'
}
}
},
mounted(){
setTimeout(() => {
filteredEstimation(){
this.$store.getters.estimations.map(function(estimation) {
estimation.offers.map(function(offer) {
offer.name = offer.name.split(" ").reverse().slice(1).reverse().join(" ");
if (offer.name.includes("first")) {
offer.description = "first option";
}
if (offer.name.includes("second")) {
offer.description = "second option";
}
if (offer.name.includes("third")) {
offer.description = "third option";
}
});
});
return !this.$store.getters.estimations ? null : this.$store.getters.estimations.filter( item => item.id == this.selected )[0].offers
}, 700);
}
}
</script>

Yields "TypeError: Cannot read property 'xxxx' of undefined" after running jest with Vue

I'm trying to make a test using jest with Vue.
the details below.
Problem:
Can't mount using shallowMount option.
Situation:
Run the test after mounting the component using shallowMount option that provides in Vue-test-utils.
Throw error "Cannot read property 'XXXX' of undefined
This is my test code.
import myComponent from '#/~';
import Vuex from 'vuex';
import Vuelidate from 'vuelidate';
import { shallowMount, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(Vuelidate);
describe('myComponent~', () => {
let store;
beforeEach(() => {
store = new Vuex.Store({
modules: {
user: {
namespaced: true,
getters: {
profile: () => {
const profile = { name: 'blahblah' };
return profile;
},
},
},
},
});
});
describe('profile.name is "blahblah"', () => {
it('return something~', () => {
const wrapper = shallowMount(myComponent, {
localVue,
store,
mocks: {
$api: {
options: {
testMethod() {
return new Promise((resolve, reject) => {
resolve('test');
});
},
},
},
$i18n: {
t() {
return {
EN: 'EN',
KO: 'KO',
JP: 'JA',
SC: 'zh-CN',
TC: 'tw-CN',
};
},
},
},
});
expect(wrapper.find('.profile').text()).toBe('blahblah');
});
I think the problem is that property isn't set as a specified value or an empty value like an array or object.
But I don't know how I set properly the properties in my logic.
For example,
when the error yields "Cannot read property 'images' of undefined",
I add to a wrapper in the relevant method like this.
exampleMethod() {
this.something = this.something.map(item => {
if (item.detailContent.images) { // <-- the added wrapper is here
~~~logic~~~~
}
})
}
But the undefined properties are so many, I also think this way is not proper.
How I do solve this problem?
added
These are details about the above example method:
exampleMethod() {
this.something = this.something.map(item => {
let passValidation = false;
let failValidation = false;
if (item.detailContent.images) {
if (this.detail.showLanguages.includes(item.code)) {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: true,
}
});
}
checkValidationPass = true;
} else {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: false,
}
});
}
checkValidationPass = false;
}
return {
...item,
required: passValidation,
warning: failValidation,
}
}
});
if (this.configId === 'OPTION2') {
this.checkOption2Validation();
} else if (this.configId === 'OPTION3') {
this.checkOption3Validation();
} else {
this.checkOption1Validation();
}
},
And this is 'this.something':
data() {
return {
something: []
}
}
The detailContent is set here.
setMethod() {
this.something = [
...this.otherthings,
];
this.something = this.something.map(item => {
let details1 = {};
if (this.configId === 'OPTION2') {
details1 = {
images: [
{ deviceType: 'PC', titleList: [null, null], imageType: 'IMAGE' },
{ deviceType: 'MOBILE', titleList: [null, null, null] }
]
};
} else if (this.configId === 'OPTION3') {
details1 = {
images: [
{ deviceType: 'PC' },
{ deviceType: 'MOBILE' }
],
links: { linkType: 'EMPTY' },
};
}
let details2 = {
mainTitle: {
content: null,
}
}
let checkValidation = false;
this.detail.detailLanguages.forEach(element => {
if (element.language === item.code) {
details1 = { ...element };
if (!!element.mainTitle) {
details2 = { ...element };
} else {
details2 = {
...details2,
...element
};
}
if (this.configId !== 'OPTION1') {
details1.images = details1.images.map(image => {
return {
...image,
required: true,
}
});
}
checkValidation = true;
}
});
return {
...item,
detailContent: this.configId !== 'OPTION1' ? details1 : details2,
required: false,
warning: false,
}
});
},

JavaScript Class: Dynamic Default Value

Trying to set creditCardExpMonth to the current month in the below Magento 2 JavaScript class (cc-form.js). The option month values are 1-12. When I manually add a month value like 3 creditCardExpMonth: 3, to the defaults:{ }, it works as expected. I just can't seem to figure out how to set it to the current month dynamically. I'm open to any solution that allows for the value to be overridden by the user's selection but I'd prefer it be inside this class or on the html page and not a JQuery update after the page loads.
I created a getCurrentMonth() function in this class but couldn't figure out how to access it correctly to set creditCardExpMonth to a default value.
define(
[
'underscore',
'Mageplaza_Osc/js/view/payment/default',
'Magento_Payment/js/model/credit-card-validation/credit-card-data',
'Magento_Payment/js/model/credit-card-validation/credit-card-number-validator',
'mage/translate'
],
function (_, Component, creditCardData, cardNumberValidator, $t) {
return Component.extend({
defaults: {
creditCardType: '',
creditCardExpYear: '',
creditCardExpMonth: '',
creditCardNumber: '',
creditCardSsStartMonth: '',
creditCardSsStartYear: '',
creditCardVerificationNumber: '',
selectedCardType: null
},
initObservable: function () {
this._super()
.observe([
'creditCardType',
'creditCardExpYear',
'creditCardExpMonth',
'creditCardNumber',
'creditCardVerificationNumber',
'creditCardSsStartMonth',
'creditCardSsStartYear',
'selectedCardType'
]);
return this;
},
initialize: function() {
var self = this;
this._super();
//Set credit card number to credit card data object
this.creditCardNumber.subscribe(function(value) {
var result;
self.selectedCardType(null);
if (value == '' || value == null) {
return false;
}
result = cardNumberValidator(value);
if (!result.isPotentiallyValid && !result.isValid) {
return false;
}
if (result.card !== null) {
self.selectedCardType(result.card.type);
creditCardData.creditCard = result.card;
}
if (result.isValid) {
creditCardData.creditCardNumber = value;
self.creditCardType(result.card.type);
}
});
//Set expiration year to credit card data object
this.creditCardExpYear.subscribe(function(value) {
creditCardData.expirationYear = value;
});
//Set expiration month to credit card data object
this.creditCardExpMonth.subscribe(function(value) {
creditCardData.expirationYear = value;
});
//Set cvv code to credit card data object
this.creditCardVerificationNumber.subscribe(function(value) {
creditCardData.cvvCode = value;
});
},
getCode: function() {
return 'cc';
},
getData: function() {
return {
'method': this.item.method,
'additional_data': {
'cc_cid': this.creditCardVerificationNumber(),
'cc_ss_start_month': this.creditCardSsStartMonth(),
'cc_ss_start_year': this.creditCardSsStartYear(),
'cc_type': this.creditCardType(),
'cc_exp_year': this.creditCardExpYear(),
'cc_exp_month': this.creditCardExpMonth(),
'cc_number': this.creditCardNumber()
}
};
},
getCcAvailableTypes: function() {
return window.checkoutConfig.payment.ccform.availableTypes[this.getCode()];
},
getIcons: function (type) {
return window.checkoutConfig.payment.ccform.icons.hasOwnProperty(type)
? window.checkoutConfig.payment.ccform.icons[type]
: false
},
getCcMonths: function() {
return window.checkoutConfig.payment.ccform.months[this.getCode()];
},
getCcYears: function() {
return window.checkoutConfig.payment.ccform.years[this.getCode()];
},
hasVerification: function() {
return window.checkoutConfig.payment.ccform.hasVerification[this.getCode()];
},
hasSsCardType: function() {
return window.checkoutConfig.payment.ccform.hasSsCardType[this.getCode()];
},
getCvvImageUrl: function() {
return window.checkoutConfig.payment.ccform.cvvImageUrl[this.getCode()];
},
getCvvImageHtml: function() {
return '<img src="' + this.getCvvImageUrl()
+ '" alt="' + $t('Card Verification Number Visual Reference')
+ '" title="' + $t('Card Verification Number Visual Reference')
+ '" />';
},
getSsStartYears: function() {
return window.checkoutConfig.payment.ccform.ssStartYears[this.getCode()];
},
getCcAvailableTypesValues: function() {
return _.map(this.getCcAvailableTypes(), function(value, key) {
return {
'value': key,
'type': value
}
});
},
getCcMonthsValues: function() {
return _.map(this.getCcMonths(), function(value, key) {
return {
'value': key,
'month': value.substring(0,2)
}
});
},
getCcYearsValues: function() {
return _.map(this.getCcYears(), function(value, key) {
return {
'value': key,
'year': value
}
});
},
getCurrentMonth: function() {
var d = new Date();
var n = d.getMonth() + 1;
return n;
},
getCurrentYear: function() {
var d = new Date();
var n = d.getYear();
return n;
},
getSsStartYearsValues: function() {
return _.map(this.getSsStartYears(), function(value, key) {
return {
'value': key,
'year': value
}
});
},
isShowLegend: function() {
return false;
},
getCcTypeTitleByCode: function(code) {
var title = '';
_.each(this.getCcAvailableTypesValues(), function (value) {
if (value['value'] == code) {
title = value['type'];
}
});
return title;
},
formatDisplayCcNumber: function(number) {
return 'xxxx-' + number.substr(-4);
},
getInfo: function() {
return [
{'name': 'Credit Card Type', value: this.getCcTypeTitleByCode(this.creditCardType())},
{'name': 'Credit Card Number', value: this.formatDisplayCcNumber(this.creditCardNumber())}
];
}
});
});
Here is the knockout HTML with select data-bind just in case it's needed (taken from Magento payment cc-form.html):
<select name="payment[cc_exp_month]"
class="select select-month"
data-bind="attr: {id: getCode() + '_expiration', 'data-container': getCode() + '-cc-month', 'data-validate': JSON.stringify({required:true, 'validate-cc-exp':'#' + getCode() + '_expiration_yr'})},
enable: isActive($parents),
options: getCcMonthsValues(),
optionsValue: 'value',
optionsText: 'month',
optionsCaption: $t('Month'),
value: creditCardExpMonth">
</select>
If this script is run every time the page loads, you could do something like this:
defaults: {
creditCardType: '',
creditCardExpYear: '',
creditCardExpMonth: (function(){
return ((new Date()).getMonth()+1);
})(),
creditCardNumber: '',
creditCardSsStartMonth: '',
creditCardSsStartYear: '',
creditCardVerificationNumber: '',
selectedCardType: null
}
or if you want something cleaner, you can refactor the code into a function that is defined prior to this object creation:
function (_, Component, creditCardData, cardNumberValidator, $t) {
function getCurrentMonth() {
return ((new Date()).getMonth()+1);
}
return Component.extend({
defaults: {
creditCardType: '',
creditCardExpYear: '',
creditCardExpMonth: getCurrentMonth(),
creditCardNumber: '',
creditCardSsStartMonth: '',
creditCardSsStartYear: '',
creditCardVerificationNumber: '',
selectedCardType: null
},

v2.canGoForward is not a function

I am trying to move the code vm.canGoForward from my controller to a service to hide the implementation details.
BEFORE CODE CHANGE
This worked fine.
View:
<button ng-disabled="!vm.canGoForward()" class="btn btn-primary" name="next" type="button" ng-click="vm.gotoStep(vm.currentStep + 1)">
Controller:
var vm = this;
vm.currentStep = 1;
vm.steps = WizardService.getWizardSteps(vm.formData);
vm.canGoForward = function() {
var res = true,
i,
nextStateIndex = vm.currentStep + 1;
if (nextStateIndex > vm.steps.length) {
return false;
}
for (i = 1; res && i <= nextStateIndex; i++) {
res = (res && vm.steps[i-1].isReady());
}
return !!res;
};
Service
var wizardService = {
getWizardSteps: getWizardSteps
};
return wizardService;
function getWizardSteps(formData) {
var wizardSteps = [
{
step: 1,
name: 'Name',
template: 'views/wizard/step1.html',
isReady: function() { return true; }
},
{
step: 2,
name: 'Email',
template: 'views/wizard/step2.html',
isReady: function() { return formData.firstName && formData.lastName; }
},
{
step: 3,
name: 'Job Category',
template: 'views/wizard/step3.html',
isReady: function() { return formData.email; }
}
];
return wizardSteps;
}
AFTER CODE CHANGE
View
Remains the same
Controller
var vm = this;
vm.currentStep = 1;
vm.steps = WizardService.getWizardSteps(vm.formData);
vm.canGoForward = WizardService.canGoForward(vm.currentStep, vm.steps);
Service
var wizardService = {
getWizardSteps: getWizardSteps,
canGoForward: canGoForward
};
return wizardService;
function getWizardSteps(formData) {
var wizardSteps = [
{
step: 1,
name: 'Name',
template: 'views/wizard/step1.html',
isReady: function() { return true; }
},
{
step: 2,
name: 'Email',
template: 'views/wizard/step2.html',
isReady: function() { return formData.firstName && formData.lastName; }
},
{
step: 3,
name: 'Job Category',
template: 'views/wizard/step3.html',
isReady: function() { return formData.email; }
}
];
return wizardSteps;
}
function canGoForward(currentStep, steps) {
console.log(steps);
var res = true,
i,
nextStateIndex = currentStep + 1;
if (nextStateIndex > steps.length) {
return false;
}
for (i = 1; res && i <= nextStateIndex; i++) {
res = (res && steps[i-1].isReady());
}
return !!res;
}
I now get the following error: TypeError: v2.canGoForward is not a function. How can I resolve it?
In your second version, the following line will actually call WizardService.canGoForward on the spot, not assign it:
vm.canGoForward = WizardService.canGoForward(vm.currentStep, vm.steps);
What gets assigned is the return value of that call, which obviously is not a function, hence the error message when a call is attempted later.
If you want to assign the function, and ensure the arguments get passed when it is called later, then use bind:
vm.canGoForward = WizardService.canGoForward.bind(WizardService, vm.currentStep, vm.steps);

Categories

Resources