I'm trying to use this module protractor-pageobject. But I can't seem to figure out how to pass elements to the constructor of the Components object. The goal is to pass element selectors to a generic class for reuse. I am able to create page objects and component object without passing in parameters, but I must be missing something in the documentation when passing parameters.
my latest attempt was:
comps: {
header: require('header.co')(els.title)
},
then in the generic class:
var Component = require('protractor-pageobject').Component;
var Header = new Component(el).extend({
els: {
title = el
}
title: function(){
this.element('title').getText();
}
setElements: function(){
els.title = elements.title;
}
});
module.exports = Header;
Related
I am trying to get drag and drop function working in the vue.js app using vue-draggable https://vuejsexamples.com/vuejs-drag-and-drop-library-without-any-dependency/
The library has few events you can listen to and I would like to execute some logic once the item is dropped. However, I am not able to access vue component 'this' along with the data and methods. I've tried to use this.$dispatch('symDragged', event); but it is not working for the same reason. 'this' is not a vue instance but rather instance of draggable element.
Here is the code:
export default {
components: {
ICol,
SymptomsChooser, MultiSelectEditor, TempPressureChooser, BodyPartsEditor, MandatorySymptomsChooser},
data() {
return {
// data ommited...
options: {
dropzoneSelector: 'ul',
draggableSelector: 'li',
excludeOlderBrowsers: true,
showDropzoneAreas: true,
multipleDropzonesItemsDraggingEnabled: true,
onDrop(event) {
// delete symptom from old basket and add it to new one
let oldBasket = event.owner.accessKey;
let newBasket = event.droptarget.accessKey;
//this is not working
//this.symDragged(this.draggedSymId, oldBasket, newBasket);
},
onDragstart(event) {
this.draggedSymId = event.items[0].accessKey;
}
}
}
},
methods: {
symDragged(symId, oldBasketId, newBasketId) {
console.log("symDragged!");
let draggedSym = this.getSymById(symId);
let basketOld = this.getBasketById(oldBasketId);
this.delSym(basketOld, draggedSym);
this.addSym({baskedId: newBaskedId, sym: draggedSym});
}
//other methods ommited
}
}
So, what is the correct way to call the vue component method from callback event? Or maybe I need to create another event so that vue instance could listen to it?
Thanks for you help!
The problem you are facing is that with this you are referencing to the returned data object scope and not component scope. The best way to solve this is to make reference to the component instance, so later on you can call anything attached to that instance. You can also take a look at codesandbox example https://codesandbox.io/embed/7kykmmmznq
data() {
const componentInstance = this;
return {
onDrop() {
let oldBasket = event.owner.accessKey;
let newBasket = event.droptarget.accessKey;
let draggedItemsAccessKeys = event.items.map(element => element.accessKey);
componentInstance.symDragged(
draggedItemsAccessKeys,
oldBasket,
newBasket
);
}
}
}
I'm trying to figure out the best way to update propsData created on a component instance. Basically I have a signature wrapper page, and receive a bunch of html which is rendered using v-html. Then I'm creating a variable number of signature pad components within that rendered html. Since I don't know what the html is going to be, I'm forced (best I can tell) to create the components on the fly after mounting.
So I'm running the following on the parent mounted():
initializeSignaturePads() {
const signatureAreas = document.querySelectorAll('.signature_area');
// dynamically create a new vue instance for each signature pad and mount onto the respective .signature_area element
// since the html is loaded via ajax, we don't know where to render this template on load, so a new Vue must be created
signatureAreas.forEach(element => {
const id = element.id;
const signatureType = element.classList.contains('initials') ? 'initials' : 'signature';
if (this.needsCustomerSignature(id)) {
let length = this.signatures.push({
fieldName: id,
valid: false,
data: null,
type: signatureType
});
const SignaturePadClass = Vue.extend(SignaturePad);
const SignaturePadInstance = new SignaturePadClass({
parent: this,
propsData: {
fieldName: id,
editable: true,
signatureType: signatureType,
signatureIndex: length - 1,
signatureData: null
}
});
// add handler for signed emit
SignaturePadInstance.$on('signed', signature => {
this.padSigned(signature);
});
// watch this for an accepted signature, then pass to each child
this.$watch('createdSignature.accepted', function (val) {
let signatureData = null;
if (val) {
signatureData = signatureType == 'signature' ? this.createdSignature.signatureData : this.createdSignature.initialsData;
}
// These two lines are the problem
SignaturePadInstance._props.signatureData = signatureData;
SignaturePadInstance._props.editable = !val;
});
SignaturePadInstance.$mount(element);
}
});
},
As far as I can tell, that propsData is now statically set on the component. But for the signatureData and editable props, I need to be able to pass that to the child components when they're updated. The watcher is working correctly and the prop is getting updated, but I'm getting the Avoid mutating a prop directly warning. Which is understandable, since I'm directly mutating the prop on the child. Is there Is there a good way to handle this?
I was able to get this figured out, after I found this stackoverflow answer. When setting props on the propsData, I was using all primitive types, so they didn't have the built in reactive getters and setters. It makes sense now that I realize that, what I was doing was the equivalent of passing a string as a prop to an component element. After I did that, the prop was reactive and I didn't have to bother with manually creating watchers.
Anyways, this was the solution:
const SignaturePadInstance = new SignaturePadClass({
parent: this,
propsData: {
fieldName: id, // << primitive
editable: true, // << primitive
signatureType: signatureType, // << primitive
signatureIndex: length - 1, // << primitive
createdSignature: this.createdSignature // << reactive object, updates to the child when changed
}
});
I used Vue.observable (VueJS 2.6 and above) to make the property reactive. Here's a complete example:
initializeSignaturePad() {
const signaturePadComponent = Vue.extend(SignaturePad)
this.instance = new signaturePadComponent()
instance._props = Vue.observable({
...instance._props,
editable: true
})
this.instance.$mount()
document.body.appendChild(instance.$el)
}
onSignatureAccepted {
this.instance.editable = false
}
I'm creating custom UI components using ES6 classes doing something like this:
class Dropdown {
constructor(dropdown) {
this.dropdown = dropdown;
this._init();
}
_init() {
//init component
}
setValue(val) {
//"public" method I want to use from another class
}
}
And when the page load I initiate the components like this:
let dropdown = document.querySelectorAll(".dropdown");
if (dropdown) {
Array.prototype.forEach.call(dropdown, (element) => {
let DropDownEl = new Dropdown(element);
});
}
But now I need to acces a method of one of these classes from another one. In this case, I need to access a method to set the value of the dropdown
based on a URL parameter, so I would like to do something like:
class SearchPage {
//SearchPage is a class and a DOM element with different components (like the dropdown) that I use as filters. This class will listen to the dispached events
//from these filters to make the Ajax requests.
constructor() {
this._page = document.querySelector(".search-page")
let dropdown = this._page.querySelector(".dropdown);
//Previously I import the class into the file
this.dropdown = new Dropdown(dropdown);
}
setValues(val) {
this.dropdown.setValue(val);
//Set other components' values...
}
}
But when I create this instance, another dropdown is added to the page, which I don't want.
I think an alternative is to create the components this way, inside other ones, and not like in the first piece of code. Is this a valid way? Should I create another Dropdown class that inherits from the original one?
A simple solution is to store the Dropdown instance on the element to avoid re-creating it:
class Dropdown {
constructor(element) {
if (element.dropdown instanceof Dropdown)
return element.dropdown;
this.element = element;
element.dropdown = this;
//init component
}
…
}
I'm using a third-party library that uses class instances with getter/setter properties, and I'd like to control those properties using a VueJS component. I'm not supposed to use the instance as component state directly (Evan You says it's "a rabbit hole"), but if I have to write my own go-between code to map component state to class state, what's the point of reactive data binding in the first place?
Example:
// Toggle.js
var Toggle = Vue.component("Toggle", {
template: '<label><input type="checkbox" v-model="visible">Click me!</label>',
props: ["visible"]
});
// Third-party library
var Widget = function() {
this._visible = true;
}
Widget.prototype = {
get visible(){ return this._visible; },
set visible(v){ this._visible = v; }
}
// app.js
var widgets = [new Widget(), new Widget(), new Widget()];
var v = new Vue({
el: "#content",
template: "<div><toggle v-for='(widget,idx) in widgets' :visible='widget.visible' :key='idx'></toggle></div>",
data: {widgets},
components: { Toggle }
});
<script src="https://vuejs.org/js/vue.js"></script>
<div id="content"></div>
The above example is not the right way to do this for two reasons: one, because props is meant to be a one-way binding from the parent down to the child, and two, because the Widget instance is a complex object that cannot be made reactive.
What's a good design pattern for this case? Remember, I don't control the Widget class so any solution must work without changing its definition.
I have 2 components - addProjectForm and listProjects. They are both nested components inside the root module. Whenever I add a project using the form, I want it to appear in the list straight away.
To achieve this, I had to pass down the controller instance to each component like this:
var RootComponent = {};
rootComponent.controller = function() {
this.example = 'test variable';
}
rootComponent.view = function(ctrl) {
return [
m.component(addProjectForm, ctrl),
m.component(listProjects, ctrl)
];
}
and then the listProjectscomponent for example, looks like this:
var listProjects = {
controller: function(root) {
this.root = root;
},
view: function(ctrl) {
console.log(ctrl.root.example);
}
};
So this way I keep calling methods on the top level, but I don't quite like passing down the controller instance like this. Is there any other way I should be doing it?
I think this is what you're looking for:
Mithril.js: Should two child components talk to each other through their parent's controller?
A newer way of solving this common problem is to use a Flux like architecture developed by Facebook:
https://facebook.github.io/flux/
Writing your own dispatcher is semi-trivial. Here's an example that someone else built alongside Mithril:
https://gist.github.com/MattMcFarland/25fb4f0241530d2f421a
The downside with this approach is it would be somewhat anti-Flux to use m.withAttr, as views aren't supposed to write directly to models in the dispatcher paradigm.
The problem you have is the difference between passing by reference or by value. In JS all primitive types are passed by value. Thats why you can't pass the string directly since it's cloned during pass. You have multiple options here:
You can use m.prop and just pass the variable down to the components, m.props stores the value in function that is always passed by reference.
var RootComponent = {};
rootComponent.controller = function() {
this.example = m.prop('test variable');
}
rootComponent.view = function(ctrl) {
return [
m.component(addProjectForm, ctrl.example),
m.component(listProjects, ctrl.example)
];
}
If the variable is an array, it will be passed by reference anyways.
Second option is to keep the list in the root context and add a callback to the second component.
var RootComponent = {};
rootComponent.controller = function() {
var projects = this.projects = [];
this.addProject = function(project) {
projects.push(project);
}
}
rootComponent.view = function(ctrl) {
return [
m.component(addProjectForm, {
onsubmit: ctrl.addProject
}),
m.component(listProjects, ctrl.projects)
];
}