I want to override the existing magento2 JS Component in my theme for some more customization.
Magento_Checkout/js/view/minicart.js
Above JS component i want to override and i want to add some more operation on the remove button event.
You can try "map" of require js. I used this and working for me. following is the requirejs-config.js inside my theme.
var config = {
map: {
'*': {
'Magento_Checkout/js/view/minicart':'js/custom/minicart'
}
}
};
Modified minicart.js file is placed inside "web/js/custom" folder inside my theme.
Just Go to your theme Override Magento_Checkout there, then under web folder make path as same as core module then add your js file & do required changes. It will reflect on frontend.
You can also extend an existing Magento JS without overwriting the whole file in your module add the require-config.js
app/code/MyVendor/MyModule/view/frontend/requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/view/minicart': {
'MyVendor_MyModule/js/minicart': true
}
}
}
};
Then add the minicart.js
app/code/MyVendor/MyModule/view/frontend/web/js/minicart.js
define([], function () {
'use strict';
return function (Component) {
return Component.extend({
/**
* #override
*/
initialize: function () {
var self = this;
return this._super();
},
MyCustomFunction: function () {
return "my function";
}
});
}
});
define(['jquery'],function ($) {
'use strict';
var mixin = {
/**
*
* #param {Column} elem
*/
initSidebar: function () {
var sidebarInitialized = false, miniCart;
miniCart = $('[data-block=\'minicart\']');
if (miniCart.data('mageSidebar')) {
miniCart.sidebar('update');
}
if (!$('[data-role=product-item]').length) {
return false;
}
miniCart.trigger('contentUpdated');
if (sidebarInitialized) {
return false;
}
sidebarInitialized = true;
miniCart.sidebar({
'targetElement': 'div.block.block-minicart',
'url': {
'checkout': window.checkout.checkoutUrl,
'update': window.checkout.updateItemQtyUrl,
'remove': window.checkout.removeItemUrl,
'loginUrl': window.checkout.customerLoginUrl,
'isRedirectRequired': window.checkout.isRedirectRequired
},
'button': {
'checkout': '#top-cart-btn-checkout',
'remove': '#mini-cart a.action.delete',
'increacseqty':'#mini-cart a.action.increase-qty',
'decreaseqty':'#mini-cart a.action.decrease-qty',
'close': '#btn-minicart-close'
},
'showcart': {
'parent': 'span.counter',
'qty': 'span.counter-number',
'label': 'span.counter-label'
},
'minicart': {
'list': '#mini-cart',
'content': '#minicart-content-wrapper',
'qty': 'div.items-total',
'subtotal': 'div.subtotal span.price',
'maxItemsVisible': window.checkout.minicartMaxItemsVisible
},
'item': {
'qty': ':input.cart-item-qty',
'button': ':button.update-cart-item'
},
'confirmMessage': $.mage.__('Are you sure you would like to remove this item from the shopping cart??')
});
return this._super();
}
};
return function (minicart) { // target == Result that Magento_Ui/.../columns returns.
return minicart.extend(mixin); // new result that all other modules receive
};
});
Related
I tried to send the output from my worker to my component.vue by window.localStorage.
Does anybody know how to show and update my worker's result in my component vue automatically?
This is my code:
worker-api.js
import Worker from "worker-loader!./worker.js";
const worker = new Worker();
worker.addEventListener('message', (e) => {
window.localStorage.setItem('result', JSON.stringify(e.data));
});
export function sendMessage(msg) {
worker.postMessage(msg);
}
worker.js
self.addEventListener("message", (e) => {
var count = e.data;
while(count < 20) {
const result = e.data + 3
self.postMessage(result);
}
});
my-component.vue
<template>
<p>Count: "{{ result }}"</p>
</template>
<script>
import Button from './Button'
import { sendMessage } from './worker-api'
export default {
name: 'my-component',
components: {Button},
data () {
return {
count : 0
}
},
computed: {
result: function () {
return JSON.parse(window.localStorage.getItem('result'))
}
},
methods: {,
postMessage() {
sendMessage(this.count)
}
},
}
</script>
It is not possible to deal with localStorage values as if they were reactive. Probably, that's why your computed property does not work.
One possible solution is to import your worker inside your component and use to update a reactive variable.
Something similar to:
component.vue
<template>
<button #click="increment">Increment Result</button>
{{ result }}
</template>
<script>
export default {
data() {
return {
// the worker path must be relative to the /public folder (in this example, the worker.js file must be at /public/worker.js)
worker: new Worker('/worker.js'),
result: 0
}
},
created() {
const self = this
this.worker.onmessage = function(event) {
self.result = event.data
}
},
methods: {
increment() {
this.worker.postMessage(this.result)
}
}
}
</script>
/public/worker.js
onmessage = function(event) {
// data sent by the Vue component is retrieved from 'data' attribute
postMessage(event.data + 1)
}
I have a middleware that exports context.isMobile. I can access it from layout like this:
layout (ctx) {
if(ctx.isMobile) {
return 'mobile'
} else if (ctx.isDesktop) {
return 'default'
}
},
...but I can't access the context from data or computed. How do I get the context there?
You can access the context via this.$nuxt.context like this:
export default {
data() {
console.log(this.$nuxt.context)
return { /*...*/ }
},
computed: {
myProp() {
console.log(this.$nuxt.context)
return 'foo'
}
}
}
As a vue newbie I wrote a test for a component. The test is green. However, when using vuetify in the component under test (v-layout, v-flex) error messages are listed in the console output. They disappear when removing vuetify in the component (v-layout, v-flex). How can I use vuetify and still avoid those messages?
The component TestForm
<script>
import "#/assets/Styles";
import {cloneDeep} from "lodash";
import VForm from "vuetify/es5/components/VForm";
import VBtn from "vuetify/es5/components/VBtn";
import {VContainer, VContent, VFlex, VLayout, VSpacer} from "vuetify/es5/components/VGrid";
import VTextField from "vuetify/es5/components/VTextField";
import {VCard, VCardText, VCardTitle} from "vuetify/es5/components/VCard";
import TestModelData from "#/api/model/example/TestModelData";
import TestData from "#/api/model/example/TestData";
import TestStatus from "#/api/model/example/TestStatus";
import TestStatusSelect from "#/components/common/TestStatusSelect";
export default {
components: {
VBtn,
VForm,
TestModelData, TestData, TestStatus, TestStatusSelect,
VCard, VCardTitle, VCardText,
VContainer, VContent, VLayout, VFlex, VSpacer,
VTextField
},
props: {
testModelData: TestModelData
},
data() {
return {
currentTestModelData: this.testModelData,
testData: this.testData ? cloneDeep(this.testData) : new TestData()
};
},
watch: {
"testModelData.testdata": function (val) {
console.log("Testdata has changed;", val);
if (val) {
this.testData = cloneDeep(val);
} else {
this.testData = new TestData();
}
}
},
computed: {
readOnly: function () {
if (this.testData.testStatus.id !== TestStatus.FIRST.id) {
return true;
} else {
return false;
}
}
},
methods: {
onFormChange(event) {
console.log("Changed: ", event);
this.$store.dispatch({
type: "testModelData/setTestData",
testData: this.testData
});
}
}
};
</script>
<template>
<v-form ref="form">
<v-layout wrap>
<v-flex xs12 lg6>
<TestStatusSelect
ref="testDataSelect"
v-model="testData.testStatus"
#change="onFormChange($event)"/>
</v-flex>
</v-layout>
</v-form>
<!-- when comment the above and uncomment the below the error messages disappear -->
<!--<v-form ref="form">-->
<!--<TestStatusSelect-->
<!--ref="testDataSelect"-->
<!--v-model="testData.testStatus"-->
<!--#change="onFormChange($event)"/>-->
<!--</v-form>-->
</template>
The jest-test
import VueTestUtils, {createLocalVue, mount} from "#vue/test-utils";
import Vuex from 'vuex';
import Vuetify from 'vuetify';
import TestForm from "#/components/example/TestForm";
import TestModelData from "#/api/model/example/TestModelData";
VueTestUtils.config.provide['$options'] = {};
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(Vuetify);
const TEST_MODEL_DATA = TestModelData.fromJSON({
"id": 1,
"testdata": {
"id": 1,
"name": "Foo",
"testStatus": 0,
}
});
describe('TestForm Tests', () => {
test('TestForm select testStatus', () => {
const setTestData = jest.fn();
const getters = {
"current": jest.fn().mockImplementation(() => {
return TEST_MODEL_DATA;
}),
"hasUnsavedChanges": jest.fn().mockReturnValue(false),
};
const store = new Vuex.Store({
modules: {
testModelData: {
namespaced: true,
getters: getters,
actions: {setTestData}
}
}
});
const wrapper = mount(TestForm, {
store, localVue, propsData: {
testModelData: TEST_MODEL_DATA
}
});
const first = wrapper.findAll('.v-list__tile--link').at(1);
first.trigger('click');
expect(setTestData).toHaveBeenCalled();
});
});
The component TestStatusSelect
<script>
import VSelect from "vuetify/es5/components/VSelect";
import TestStatus from "#/api/model/example/TestStatus";
export default {
components: {
VSelect
},
props: ["value", "disabled"],
data() {
return {
testStatuses: TestStatus.ALL,
testStatus: this.value ? this.value : TestStatus.FIRST
};
},
watch: {
value(val) {
if (this.testStatus.id !== val.id) {
console.log('VALUE');
this.testStatus = val;
}
},
testStatus(val, oldVal) {
if (val.id !== oldVal.id) {
this.$emit("input", val);
this.$emit("change", val);
}
}
}
};
</script>
<template>
<v-select
ref="testStatusSelect"
:disabled="disabled"
label="Result"
:items="testStatuses"
item-text="name"
item-value="id"
v-model="testStatus"
return-object>
</v-select>
</template>
The class TestModelData
import TestData from "#/api/model/example/TestData";
class TestModelData {
constructor() {
this.id = null;
this.testData = null;
}
fromJSON(json) {
this.id = json.id;
this.testData = TestData.fromJSON(json.testData);
}
toJSON() {
const o = {
id: this.id,
};
if (this.testData) {
a.testData = this.testData.toJSON();
}
return o;
}
static fromJSON(json) {
if (!json) {
return null;
} else {
const a = new TestModelData();
a.fromJSON(json);
return a;
}
}
}
export default TestModelData;
The class TestData
import TestStatus from "#/api/model/example/TestStatus";
class TestData {
constructor() {
this.id = null;
this.name = null;
this.testStatus = TestStatus.FIRST;
}
fromJSON(json) {
this.id = json.id;
this.name = json.name;
this.testStatus = json.testStatus !== null ? TestStatus.fromJSON(json.testStatus) : null;
}
toJSON() {
const o = {
id: this.id,
};
o.name = this.name;
o.testStatus = this.testStatus ? this.testStatus.toJSON() : null;
return o;
}
static fromJSON(json) {
if (!json) {
return null;
} else {
const a = new TestData();
a.fromJSON(json);
return a;
}
}
}
export default TestData;
The class TestStatus
import PropTypes from "prop-types";
import Definition from "../Definition";
class TestStatus extends Definition {
constructor(id, name) {
super();
this.id = id;
this.name = name;
}
static FIRST = new TestStatus(0, "first");
static SECOND = new TestStatus(1, "second");
static ALL = [
TestStatus.FIRST,
TestStatus.SECOND
];
toJSON() {
return this.id;
}
static fromJSON(json) {
if (json === TestStatus.FIRST.id) {
return TestStatus.FIRST;
}
else if (json === TestStatus.SECOND.id) {
return TestStatus.SECOND;
}
console.error("TestStatus unknown", json);
throw new Error(`TestStatus ${json} unknown`, json);
}
}
TestStatus.prototype.PROPTYPES = {
id: PropTypes.number,
name: PropTypes.string,
};
export default TestStatus;
The console output
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: an extended child component <VBtn> has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the stubs mounting option.
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: an extended child component <VCard> has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the stubs mounting option.
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: an extended child component <VCardTitle> has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the stubs mounting option.
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: an extended child component <VCardText> has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the stubs mounting option.
console.warn node_modules/vuetify/es5/util/console.js:32
[Vuetify] Unable to locate target [data-app]
found in
---> <VMenu>
<VSelect>
<TestStatusSelect>
<VForm>
<VCard>
<Anonymous>
<Root>
console.error node_modules/vue/dist/vue.common.js:593
[Vue warn]: $listeners is readonly.
found in
---> <VSelect>
<TestStatusSelect>
<VForm>
<VCard>
<Anonymous>
<Root>
console.log src/components/example/TestForm.vue:800
Changed: TestStatus {
_clazz: [Getter/Setter],
id: [Getter/Setter],
name: [Getter/Setter] }
console.error node_modules/vue/dist/vue.common.js:593
[Vue warn]: $listeners is readonly.
found in
---> <VSelect>
<TestStatusSelect>
<VForm>
<VCard>
<Anonymous>
<Root>
console.error node_modules/vue/dist/vue.common.js:593
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
found in
---> <VSelect>
<TestStatusSelect>
<VForm>
<VCard>
<Anonymous>
<Root>
console.error node_modules/vue/dist/vue.common.js:593
[Vue warn]: $listeners is readonly.
found in
---> <VSelect>
<TestStatusSelect>
<VForm>
<VCard>
<Anonymous>
<Root>
There are a few solutions / work arounds in this thread.
This is the one that worked for me, I added it at the top of my test body:
document.body.setAttribute('data-app', true)
find the button by id and triggering a click may occur the warning to resolve this issue consider the below code
let element_forgetBtn = wrapper.find("#forgotPasswordBtn");
> let app = document.createElement("div");
> app.setAttribute("data-app", true);
> document.body.append(app);
element_forgetBtn.trigger("click");
complete code is below
let element_forgetBtn = wrapper.find("#forgotPasswordBtn");
let app = document.createElement("div");
app.setAttribute("data-app", true);
document.body.append(app);
element_forgetBtn.trigger("click");
i try to connect my frontend to my backend,
the request is done correctly i received the correct data, but the DOM is not updating. I use this.$nextTick but it doesn't affect the update
in the template i use {{ system.CPU.avgload }}
like i said the fetch is done correctly it pass into nexttick, but nothing change
in the main vue i have this
import System from '../utils/system'
import Auth from '../utils/auth'
export default {
created: function () {
this.system = {
CPU: {
avgload: 0
}
}
},
mounted: function () {
this.fetchData()
setInterval(function () {
this.fetchData()
}.bind(this), 10000)
},
methods: {
fetchData () {
if (!Auth.checkAuth) {
console.log('test')
this.error = true
} else {
var self = this
this.$nextTick(function () {
System.Get(function (response) {
self.system = response
})
})
}
}
}
}
and the template is
<div class="text-xs-left" id="example-caption-1">CPU : {{ system.CPU.avgload }} %</div>
You have to add variable system in the data section of vue instance. Than only this variable will become reactive and available in the HTML.
export default {
data: function () {
return { system: {
CPU: {
avgload : ""
}
}
}
}
...
...
I'm new to backbone.js and express and I have been adapting Christophe Coenraets Wine Cellar REST API example application for my own project.
I am building a form that has several menus needing to be populated from multiple unrelated collections in mongodb.
I am able to populate one menu with one collection, but I have no idea how to get more than one collection to my form View.
Here are the files I am using to populate one menu. How do I expand this to populate two menus?
I suppose I could make a new View for every menu I want to populate - but that seems like overkill.
Can I combine two mongodb find() collections into one object, and list them separately on a page? If so, how?
thanks in advance!
/routes/modules.js contains:
exports.findAllModules = function(req, res) {
db.collection('modules', function(err, collection) {
collection.find().toArray(function(err, items) {
res.send(items);
});
});
};
/server.js contains:
app.get('/modules', module.findAllModules);
/public/js/main.js contains:
routes: {
"modules" : "list" }
...
list: function(page) {
var p = page ? parseInt(page, 10) : 1;
var moduleList = new ModuleCollection();
moduleList.fetch({success: function(){
console.log('in list function');
$("#content").html(new ModuleListView({model: moduleList, page: p}).el);
}});
this.headerView.selectMenuItem('home-menu');
},
...
utils.loadTemplate([
'ModuleListItemView' ], function() {
app = new AppRouter();
Backbone.history.start(); });
/public/models/models.js contains:
window.Module = Backbone.Model.extend({
urlRoot: "/modules",
idAttribute: "_id",
initialize: function () {
this.validators = {};
this.validators.name = function (value) {
return value.length > 0 ? {isValid: true} : {isValid: false, message: "You must enter a name"};
};
validateItem: function (key) {
return (this.validators[key]) ? this.validators[key](this.get(key)) : {isValid: true};
},
validateAll: function () {
var messages = {};
for (var key in this.validators) {
if(this.validators.hasOwnProperty(key)) {
var check = this.validators[key](this.get(key));
if (check.isValid === false) {
messages[key] = check.message;
}
}
}
return _.size(messages) > 0 ? {isValid: false, messages: messages} : {isValid: true};
},
defaults: {
_id: null,
name: ""
} });
window.ModuleCollection = Backbone.Collection.extend({
model: Module,
url: "/modules"
});
/public/js/views/modulelist.js contains:
window.ModuleListView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
var modules = this.model.models;
$(this.el).html('<ul class="thumbnails"></ul>');
for (var i = 0; i < modules.length; i++) {
$('.thumbnails', this.el).append(new ModuleListItemView({model: modules[i]}).render().el);
}
return this;
} });
window.ModuleListItemView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
} });
/public/tpl/ModuleListView.html contains:
Not entirely sure how your code works, but here are a few backbone tips.
If you wanna build a menu from a collection don't pass the collection as a model.
Instead of:
$("#content").html(new ModuleListView({model: moduleList, page: p}).el);
Use:
$("#content").empty().append(new ModuleListView({collection: moduleList, page: p}).el);
Instead of:
render: function () {
var modules = this.model.models;
$(this.el).html('<ul class="thumbnails"></ul>');
for (var i = 0; i < modules.length; i++) {
$('.thumbnails', this.el).append(new ModuleListItemView({model: modules[i]}).render().el);
}
return this;
}
Use:
render: function () {
this.$el.html('<ul class="thumbnails">');
this.collection.each(function(model) {
this.$('.thumbnails').append(new ModuleListItemView({model: model}).render().el);
}, this);
return this;
}
If you have no need in updating or deleting your models, it's enough to add the url path /modules only to the collection, for reading the initial modules.