Related
I'm currently struggling with this issue when trying to build the administration on my shopware6 instance.
I am not sure to understand what it's expecting with this error
Definition for rule 'vue/require-prop-types' was not found vue/require-prop-types
This is pointing the line 22 which is :
props: {
Here's the index.js file :
import './custom-entity-single-select.scss';
import template from './custom-entity-single-select.html.twig';
const { Component, Mixin, Utils } = Shopware;
const { Criteria, EntityCollection } = Shopware.Data;
const { debounce, get } = Shopware.Utils;
Component.register('custom-entity-single-select', {
template,
inject: { repositoryFactory: 'repositoryFactory', feature: 'feature' },
mixins: [
Mixin.getByName('remove-api-error'),
],
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
required: false,
},
highlightSearchTerm: {
type: Boolean,
required: false,
default: true,
},
placeholder: {
type: String,
required: false,
default: '',
},
resetOption: {
type: String,
required: false,
default: '',
},
labelProperty: {
type: [String, Array],
required: false,
default: 'name',
},
labelCallback: {
type: Function,
required: false,
default: null,
},
entity: {
required: true,
type: String,
},
resultLimit: {
type: Number,
required: false,
default: 25,
},
criteria: {
type: Object,
required: false,
default() {
return (new Criteria(1, this.resultLimit)).getAssociation('stateMachine').addFilter(Criteria.equals('state_machine_state.stateMachine.technicalName', 'order_transaction.state'));
},
},
context: {
type: Object,
required: false,
default() {
return Shopware.Context.api;
},
},
disableAutoClose: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
searchTerm: '',
isExpanded: false,
resultCollection: null,
singleSelection: null,
isLoading: false,
// used to track if an item was selected before closing the result list
itemRecentlySelected: false,
lastSelection: null,
};
},
computed: {
inputClasses() {
return {
'is--expanded': this.isExpanded,
};
},
selectionTextClasses() {
return {
'is--placeholder': !this.singleSelection,
};
},
repository() {
return this.repositoryFactory.create(this.entity);
},
/**
* #returns {EntityCollection}
*/
results() {
return this.resultCollection;
},
},
watch: {
value(value) {
// No need to fetch again when the new value is the last one we selected
if (this.lastSelection && this.value === this.lastSelection.id) {
this.singleSelection = this.lastSelection;
this.lastSelection = null;
return;
}
if (value === '' || value === null) {
this.singleSelection = null;
return;
}
this.loadSelected();
},
},
created() {
this.createdComponent();
},
methods: {
createdComponent() {
this.loadSelected();
},
/**
* Fetches the selected entity from the server
*/
loadSelected() {
if (!this.value) {
if (this.resetOption) {
this.singleSelection = {
id: null,
name: this.resetOption,
};
}
return Promise.resolve();
}
this.isLoading = true;
return this.repository.get(this.value, { ...this.context, inheritance: true }, this.criteria).then((item) => {
this.criteria.setIds([]);
this.singleSelection = item;
this.isLoading = false;
return item;
});
},
createCollection(collection) {
return new EntityCollection(collection.source, collection.entity, collection.criteria);
},
isSelected(item) {
return item.id === this.value;
},
debouncedSearch: debounce(function updateSearchTerm() {
this.search();
}, 400),
search() {
if (this.criteria.term === this.searchTerm) {
return Promise.resolve();
}
this.criteria.setPage(1);
this.criteria.setLimit(this.resultLimit);
this.criteria.setTerm(this.searchTerm);
this.resultCollection = null;
const searchPromise = this.loadData().then(() => {
this.resetActiveItem();
});
this.$emit('search', searchPromise);
return searchPromise;
},
paginate() {
if (!this.resultCollection || this.resultCollection.total < this.criteria.page * this.criteria.limit) {
return;
}
this.criteria.setPage(this.criteria.page + 1);
this.loadData();
},
loadData() {
this.isLoading = true;
return this.repository.search(this.criteria, { ...this.context, inheritance: true }).then((result) => {
this.displaySearch(result);
this.isLoading = false;
return result;
});
},
displaySearch(result) {
if (!this.resultCollection) {
this.resultCollection = result;
} else {
result.forEach(item => {
// Prevent duplicate entries
if (!this.resultCollection.has(item.id)) {
this.resultCollection.push(item);
}
});
}
if (this.resetOption) {
if (!this.resultCollection.has(null)) {
this.resultCollection.unshift({
id: null,
name: this.resetOption,
});
}
}
},
displayLabelProperty(item) {
if (typeof this.labelCallback === 'function') {
return this.labelCallback(item);
}
const labelProperties = [];
if (Array.isArray(this.labelProperty)) {
labelProperties.push(...this.labelProperty);
} else {
labelProperties.push(this.labelProperty);
}
return labelProperties.map(labelProperty => {
return this.getKey(item, labelProperty) || this.getKey(item, `translated.${labelProperty}`);
}).join(' ');
},
onSelectExpanded() {
this.isExpanded = true;
// Always start with a fresh list when opening the result list
this.criteria.setPage(1);
this.criteria.setLimit(this.resultLimit);
this.criteria.setTerm('');
this.resultCollection = null;
this.loadData().then(() => {
this.resetActiveItem();
});
// Get the search text of the selected item as prefilled value
this.searchTerm = this.tryGetSearchText(this.singleSelection);
this.$nextTick(() => {
this.$refs.customSelectInput.select();
this.$refs.customSelectInput.focus();
});
},
tryGetSearchText(option) {
if (typeof this.labelCallback === 'function') {
return this.labelCallback(option);
}
let searchText = this.getKey(option, this.labelProperty, '');
if (!searchText) {
searchText = this.getKey(option, `translated.${this.labelProperty}`, '');
}
return searchText;
},
onSelectCollapsed() {
// Empty the selection if the search term is empty
if (this.searchTerm === '' && !this.itemRecentlySelected) {
this.clearSelection();
}
this.$refs.customSelectInput.blur();
this.searchTerm = '';
this.itemRecentlySelected = false;
this.isExpanded = false;
},
closeResultList() {
this.$refs.selectBase.collapse();
},
setValue(item) {
this.itemRecentlySelected = true;
if (!this.disableAutoClose) {
this.closeResultList();
}
// This is a little against v-model. But so we dont need to load the selected item on every selection
// from the server
this.lastSelection = item;
this.$emit('change', item.id, item);
this.$emit('option-select', Utils.string.camelCase(this.entity), item);
},
clearSelection() {
this.$emit('before-selection-clear', this.singleSelection, this.value);
this.$emit('change', null);
this.$emit('option-select', Utils.string.camelCase(this.entity), null);
},
resetActiveItem(pos = 0) {
// Return if the result list is closed before the search request returns
if (!this.$refs.resultsList) {
return;
}
// If an item is selected the second entry is the first search result
if (this.singleSelection) {
pos = 1;
}
this.$refs.resultsList.setActiveItemIndex(pos);
},
onInputSearchTerm(event) {
const value = event.target.value;
this.$emit('search-term-change', value);
this.debouncedSearch();
},
getKey(object, keyPath, defaultValue) {
return get(object, keyPath, defaultValue);
},
},
});
And here you can find the whole gist containing this index.js, the html.twig file and the scss : https://gist.github.com/Youmar0504/2154bd1d16866d14644aa2a3a6fd513f
ALREADY TRIED
value: {
type: String,
required: false,
default: '',
},
value: {
type: Array,
required: false,
default: [],
},
I would assume you have an extra closing '}' at the value prop.
props: {
value: {
required: false,
} <--
},
highlightSearchTerm: {
type: Boolean,
required: false,
default: true,
},
placeholder: {
type: String,
required: false,
default: '',
},
resetOption: {
type: String,
required: false,
default: '',
},
labelProperty: {
type: [String, Array],
required: false,
default: 'name',
},
labelCallback: {
type: Function,
required: false,
default: null,
},
entity: {
required: true,
type: String,
},
resultLimit: {
type: Number,
required: false,
default: 25,
},
criteria: {
type: Object,
required: false,
default() {
return (new Criteria(1, this.resultLimit)).getAssociation('stateMachine').addFilter(Criteria.equals('state_machine_state.stateMachine.technicalName', 'order_transaction.state'));
},
},
context: {
type: Object,
required: false,
default() {
return Shopware.Context.api;
},
},
disableAutoClose: {
type: Boolean,
required: false,
default: false,
},
},
I'm new to VueJS.
My component's template and the props are below
<template>
<div>
<label>WorkHours</label>
<div v-for="(data, day) in value.config.workingHours">
<label>{{day}}</label>
<hour-range-selector
:value="[data.timeFrom, data.timeTo]"
class="rangeText"
:mandatory="true"
:placeholder="placeholder"
:full-range="['00:00', '23:59']"
#input="(val) => workingHoursChanged(val, day)"
/>
</div>
</div>
</template>
<script>
import HourRangeSelector from '..../HoursRangeSelector';
export default {
name: 'WorkingDaysSelector',
components: {HourRangeSelector},
props: {
value: {
config:{
workingHours: {
monday: {
available: false,
timeFrom: '',
timeTo: '',
},
tuesday: {
available: false,
timeFrom: '',
timeTo: '',
},
wednesday: {
available: false,
timeFrom: '',
timeTo: '',
},
thursday: {
available: false,
timeFrom: '',
timeTo: '',
},
friday: {
available: false,
timeFrom: '',
timeTo: '',
},
saturday: {
available: false,
timeFrom: '',
timeTo: '',
},
sunday: {
available: false,
timeFrom: '',
timeTo: '',
}
}
}
},
placeholder: {
type: Array,
default: () => (['From', 'To']),
},
},
data() {
return {
fullRange: ['00:00', '23:59'],
};
},
methods:{
workingHoursChanged(val, day){
//IN PROGRESS
}
};
</script>
<style scoped>
</style>
If I run the code, I'm getting
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in render: "TypeError: Cannot read property 'workingHours' of undefined"
Am I approaching correctly? Or How do I achieve this?
Props are the data passed from the parent component to the child one, if you want to define local properties you should define them inside the data option like :
export default {
name: 'WorkingDaysSelector',
components: {HourRangeSelector},
props: {
placeholder: {
type: Array,
default: () => (['From', 'To']),
},
},
data() {
return {
value: {
config:{
workingHours: {
monday: {
available: false,
timeFrom: '',
timeTo: '',
},
tuesday: {
available: false,
timeFrom: '',
timeTo: '',
},
wednesday: {
available: false,
timeFrom: '',
timeTo: '',
},
thursday: {
available: false,
timeFrom: '',
timeTo: '',
},
friday: {
available: false,
timeFrom: '',
timeTo: '',
},
saturday: {
available: false,
timeFrom: '',
timeTo: '',
},
sunday: {
available: false,
timeFrom: '',
timeTo: '',
}
}
}
},
fullRange: ['00:00', '23:59'],
};
},
methods:{
workingHoursChanged(val, day){
//IN PROGRESS
}
};
</script>
<style scoped>
</style>
I create a simple Vue.js plugin, and want to test that with npm and jest.
One function should return a document element by id, but it return always null.
Why is that and how can i fix it?
// plugintest.js
const PluginTest = {
install(Vue, options) {
Vue.mixin({
methods: {
getBool: function() { return true; },
getElem: function(id) { return document.getElementById(id); }
}
});
}
}
export default PluginTest;
//plugintest.spec.js
let Vue = require('vue/dist/vue')
import PluginTest from './plugintest.js';
Vue.use(PluginTest);
describe("plugintest.js", () => {
test('test functions', () => {
const wrapper = new Vue({
template: '<div id="div1">Hello, World!</div>' }).$mount();
expect(wrapper.getBool()).toBe(true);
expect(wrapper.getElem('div1')).not.toBe(null); // FAIL!!!
});
});
Error message:
expect(received).not.toBe(expected) // Object.is equality
Expected: not null
19 |
20 | expect(wrapper.getBool()).toBe(true);
> 21 | expect(wrapper.getElem('div1')).not.toBe(null);
| ^
22 | });
23 | });
24 |
at Object.toBe (plugintest.spec.js:21:39)
console.log(wrapper.$el) output:
HTMLDivElement {
__vue__:
Vue {
_uid: 1,
_isVue: true,
'$options':
{ components: {},
directives: {},
filters: {},
_base: [Object],
methods: [Object],
template: '<div id="div1">Hello, World!</div>',
render: [Function: anonymous],
staticRenderFns: [] },
_renderProxy:
Vue {
_uid: 1,
_isVue: true,
'$options': [Object],
_renderProxy: [Circular],
_self: [Circular],
'$parent': undefined,
'$root': [Circular],
'$children': [],
'$refs': {},
_watcher: [Object],
_inactive: null,
_directInactive: false,
_isMounted: true,
_isDestroyed: false,
_isBeingDestroyed: false,
_events: {},
_hasHookEvent: false,
_vnode: [Object],
_staticTrees: null,
'$vnode': undefined,
'$slots': {},
'$scopedSlots': {},
_c: [Function],
'$createElement': [Function],
'$attrs': [Getter/Setter],
'$listeners': [Getter/Setter],
_watchers: [Object],
getBool: [Function: bound getBool],
getElem: [Function: bound getElem],
_data: {},
'$el': [Circular] },
_self: [Circular],
'$parent': undefined,
'$root': [Circular],
'$children': [],
'$refs': {},
_watcher:
Watcher {
vm: [Circular],
deep: false,
user: false,
lazy: false,
sync: false,
before: [Function: before],
cb: [Function: noop],
id: 2,
active: true,
dirty: false,
deps: [],
newDeps: [],
depIds: Set {},
newDepIds: Set {},
expression: 'function () {\n vm._update(vm._render(), hydrating);\n }',
getter: [Function: updateComponent],
value: undefined },
_inactive: null,
_directInactive: false,
_isMounted: true,
_isDestroyed: false,
_isBeingDestroyed: false,
_events: {},
_hasHookEvent: false,
_vnode:
VNode {
tag: 'div',
data: [Object],
children: [Object],
text: undefined,
elm: [Circular],
ns: undefined,
context: [Circular],
fnContext: undefined,
fnOptions: undefined,
fnScopeId: undefined,
key: undefined,
componentOptions: undefined,
componentInstance: undefined,
parent: undefined,
raw: false,
isStatic: false,
isRootInsert: true,
isComment: false,
isCloned: false,
isOnce: false,
asyncFactory: undefined,
asyncMeta: undefined,
isAsyncPlaceholder: false },
_staticTrees: null,
'$vnode': undefined,
'$slots': {},
'$scopedSlots': {},
_c: [Function],
'$createElement': [Function],
'$attrs': [Getter/Setter],
'$listeners': [Getter/Setter],
_watchers: [ [Object] ],
getBool: [Function: bound getBool],
getElem: [Function: bound getElem],
_data: {},
'$el': [Circular] } }
Try using vm.$el.
expect(wrapper.$el).not.toBe(null);
The root DOM element that the Vue instance is managing.
Reference
NOTICE
If you want to access another elements, your code could look something like this:
expect(wrapper.$el.querySelector("div")).not.toBe(null);
Another option is to use VueTestUtils's find mthod: https://vue-test-utils.vuejs.org/api/wrapper/find.html#find
Using VueTestUtils
Try using attachToDocument option.
Reference
import { mount, createLocalVue } from "#vue/test-utils";
import PluginTest from "./plugintest.js";
const localVue = createLocalVue();
localVue.use(PluginTest);
it("plugintest.js", () => {
const component = {
template: "<div id="div1">Hello, World!</div>"
}
const wrapper = mount(component, {
localVue,
attachToDocument: true
});
expect(wrapper.vm.getElem("div1")).not.toBe(null);
});
I think you'r missing the parameter for getElem(), try it like:
expect(wrapper.getElem('div1')).not.toBe(null);
I defined an object in data as
export default {
data() {
return {
labelPosition: 'right',
isText: false,
isDate: false,
isExam: false,
isFile: false,
isWrite: false,
stepLists: [],
flowId: '',
txtName: '',
form: {
textName: '',
textPosition: '',
}
the html like this :
when I change the form.textName ,I found it doesn't work
this.$set(this.form, 'textName', temp.name) //not work
this.form={textName:'abc'}
this.form = Object.assign({}, this.form)
//not work
this.$set(this.form,'textName', '---------------------') work well.
I have jqgrid, which sends row's data to another view (MVC4) when row is selected. But when I edit row's info (I'm using inline edit) this view doesn't changed. And I can't find event that is taking place after inline edit. This is js, what should I change to change my view after row editing
$(function () {
$("#GridTable").jqGrid({
url: "/Goods/GoodsList",
editurl: "/Goods/Edit",
datatype: 'json',
mtype: 'Get',
colNames: ['GoodId', 'Имя', 'Цена'],
colModel: [
{ key: true, hidden: true, name: 'GoodId', index: 'GoodId', editable: true },
{
key: false, name: 'GoodName', index: 'GoodName', editable: true, sortable: true,
editrules: {
required: true, custom: true, custom_func: notATag
}
},
{
key: false, name: 'Price', index: 'Price', editable: true, sortable: true, formatter: numFormat,
unformat: numUnformat,
//sorttype: 'float',
editrules: { required: true, custom: true, custom_func: figureValid}
}, ],
pager: jQuery('#pager'),
rowNum: 10,
rowList: [10, 25, 50, 100],
height: '100%',
viewrecords: true,
caption: 'Список товаров',
sortable: true,
emptyrecords: 'No records to display',
cellsubmit : 'remote',
jsonReader: {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
Id: "0"
},
//to get good's full view when row is selected
onSelectRow:
function () {
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
},
//to change good's full view after row deleting
loadComplete: function(data){
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
},
autowidth: true,
multiselect: false
}).navGrid('#pager', { edit: false, add: true, del: true, search: false, refresh: true },
{
// edit options
zIndex: 100,
url: '/Goods/Edit',
closeOnEscape: true,
closeAfterEdit: true,
recreateForm: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
}
},
{
// add options
zIndex: 100,
url: "/Goods/Create",
closeOnEscape: true,
closeAfterAdd: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
},
{
// delete options
zIndex: 100,
url: "/Goods/Delete",
closeOnEscape: true,
closeAfterDelete: true,
recreateForm: true,
msg: "Are you sure you want to delete this task?",
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
});
$('#GridTable').inlineNav('#pager', {
edit: true,
add: false,
del: false,
cancel: true,
editParams: {
keys: true,
afterSubmit: function (response) {
if (response.responseText) {
alert(response.responseText);
}
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
}
},
});
});
The properties and callback function of editParams parameter of inlineNav can be found here. What you need is probably aftersavefunc or successfunc instead of afterSubmit, which exist only in form editing method (see here). The parameters of aftersavefunc or successfunc callbacks are described here (as parameters of saveRow), but the parameters depend on the version of jqGrid, which you use and from the fork of jqGrid (free jqGrid, commercial Guriddo jqGrid JS or an old jqGrid in version <=4.7). I develop free jqGrid fork and I would recommend you to use the current (4.13.3) version of free jqGrid.