I was wondering if anyone knew how to do the equivalent of 'keydown' and 'keyup' events with WebdriverIO? I currently have the following code:
// required pages
var LibraryPage = require('../../pageobjects/library.page.js');
describe('Delete Button', function(){
before(function (){
LibraryPage.open('/library/list/1/');
});
it('Delete button shows correct number of images', function (){
browser.keys('Ctrl');
LibraryPage.Image('asset1.jpg').click();
LibraryPage.Image('asset3.jpg').click();
LibraryPage.Image('asset5.jpg').click();
expect(LibraryPage.DeleteButton.getAttribute("title")).to.equal("Delete 3 assets");
});
});
// Library Page Object
DeleteButton: { get: function () { return browser.element('div[title^="TODO: Delete "]'); } },
Asset: { value: function(assetName) { return browser.element('tr*=' + assetName); } },
I want to hold the CTRL key while selecting images to multi-select.
What I have read indicates that the keys function should be sticky until it gets nulled out, but that’s not what I am experiencing.. Each item gets clicked without the CTRL button being held.
Using WebdriverIO v4 (synchronous JS).
Any help would be much appreciated, thanks!
Related
I'm using ag-grid (javascript) to display a large amount of rows (about 3,000 or more) and allow the user to enter values and it should auto-save them as the user goes along. My current strategy is after detecting that a user makes a change to save the data for that row.
The problem I'm running into is detecting and getting the correct values after the user enters a value. The onCellKeyPress event doesn't get fired for Backaspace or Paste. However if I attach events directly to DOM fields to catch key presses, I don't know how to know what data the value is associated with. Can I use getDisplayedRowAtIndex or such to be able to reliably do this reliably? What is a good way to implement this?
EDIT: Additional detail
My current approach is to capture onCellEditingStopped and then getting the data from the event using event.data[event.column.colId]. Since I only get this event when the user moves to a different cell and not just if they finish typing I also handle the onCellKeyPress and get the data from event.event.target (since there is no event.data when handling this event). Here is where I run into a hard-to-reproduce problem that event.event.target is sometimes undefined.
I also looked at using forEachLeafNode method but it returns an error saying it isn't supported when using infinite row model. If I don't use infinite mode the load time is slow.
It looks like you can bind to the onCellKeyDown event. This is sometimes undefined because on first keydown the edit of agGrid will switch from the cell content to the cell editor. You can wrap this around to check if there is a cell value or cell textContent.
function onCellKeyDown(e) {
console.log('onCellKeyDown', e);
if(e.event.target.value) console.log(e.event.target.value)
else console.log(e.event.target.textContent)
}
See https://plnkr.co/edit/XhpVlMl7Jrr7QT4ftTAi?p=preview
As been pointed out in comments, onCellValueChanged might work, however
After a cell has been changed with default editing (i.e. not your own custom cell renderer), the cellValueChanged event is fired.
var gridOptions = {
rowData: null,
columnDefs: columnDefs,
defaultColDef: {
editable: true, // using default editor
width: 100
},
onCellEditingStarted: function(event) {
console.log('cellEditingStarted', event);
},
onCellEditingStopped: function(event) {
console.log('cellEditingStopped', event);
},
onCellValueChanged: function(event) {
console.log('cellValueChanged', event);
}
};
another option could be to craft your own editor and inject it into cells:
function MyCellEditor () {}
// gets called once before the renderer is used
MyCellEditor.prototype.init = function(params) {
this.eInput = document.createElement('input');
this.eInput.value = params.value;
console.log(params.charPress); // the string that started the edit, eg 'a' if letter a was pressed, or 'A' if shift + letter a
this.eInput.onkeypress = (e) => {console.log(e);} // check your keypress here
};
// gets called once when grid ready to insert the element
MyCellEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
MyCellEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
MyCellEditor.prototype.onKeyDown = (e) => console.log(e);
// returns the new value after editing
MyCellEditor.prototype.getValue = function() {
return this.eInput.value;
};
//// then, register it with your grid:
var gridOptions = {
rowData: null,
columnDefs: columnDefs,
components: {
myEditor: MyCellEditor,
},
defaultColDef: {
editable: true,
cellEditor: 'myEditor',
width: 100
},
onCellEditingStarted: function(event) {
console.log('cellEditingStarted', event);
},
onCellEditingStopped: function(event) {
console.log('cellEditingStopped', event);
}
};
We are using backbone dynamic forms.I have a textfield, on click to that field, a pop up opens up. This is my functionality.
But on double click to that field screen getting faded and no way to get back. Again new window has to be opened for working.
The solution has to support any browser mainly Chrome, Firefox, IE
Using a count variable we can resolve this. Just follow the below steps:
1. Initialise the count variable to 0.
2. If count is 0, then only trigger the lookup event
3. Make the count variable to 1 on click to field, which triggers event to show lookup
4. On close to lookup panel again make the count to 0.
Sample code:
## MainFormView.js ##
count:0,
initialize: function (options) {this.count = 0;},
events: {
'click #lookupId': 'showLookup'
},
showLookup: function (e) {
e.stopPropagation();
if(this.count == 0){
this.count = 1;
this.trigger("show:list");
}
}
### LookupView.js ###
lookupView: null,
initialize: function (options) {
this.lookupView= options.renderedFormView;
},
closePanel: function() {
this.lookupView.count = 0;
}
## MainController.js ##
mainFormView.on("show:list", function () {
var lookupView = new LookupView({
model: staffList,
renderedFormView: mainFormView
});
});
Following is my js code which works fine when i add new values or give enter space etc. Basically the following code calls a function after 5 seconds after the last key is pressed to save textarea values. The issue I am facing is the function does not make any call if i use editor to change the content within itself like bold the text, underline etc without pressing any key on the editor. Kindly let me know how can I modify the following code so it gets trigger on last change after 5 seconds?
CKEDITOR.replace( 'editor1', {
on: {
instanceReady: function() {
},
key: function() {
onautosave(); // Function which makes call after 5 seconds to save values
}
}
});
function onautosave(){
if(autosave_timer)
clearTimeout(autosave_timer);
autosave_timer = setTimeout(save, 5000);
}
Try using the on change event, that might work better for you:
CKEDITOR.replace('editor1', {
on: {
instanceReady: function() { },
change: function() {
onautosave(); // Function which makes call after 5 seconds to save values
}
}
});
function onautosave() {
if (autosave_timer) {
clearTimeout(autosave_timer);
}
autosave_timer = setTimeout(save, 5000);
}
I'm learning about Session and reactive data sources in Meteor JS. They work great for setting global UI states. However, I can't figure out how to scope them to a specific instance of a template.
Here's what I'm trying to do
I have multiple contenteditable elements on a page. Below each is an "Edit" button. When the user clicks on the Edit button, it should focus on the element and also show "Save" and "Cancel" buttons.
If the user clicks "Cancel", then any changes are eliminated, and the template instance should rerender with the original content.
Here's the code I have so far
// Helper
Template.form.helpers({
editState: function() {
return Session.get("editState");
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get("editState")) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", null);
},
});
// Template
<template name="form">
<div class="{{editState}}">
<p class="form-field" contenteditable>
{{descriptionText}}
</p>
</div>
Edit
Save
Cancel
</template>
// CSS
.edit-btn
.cancel-btn,
.save-btn {
display: inline-block;
}
.cancel-btn,
.save-btn {
display: none;
}
.is-editing .cancel-btn,
.is-editing .save-btn {
display: inline-block;
}
The problem
If I have more than one instance of the Form template, then .form-field gets focused for each one, instead of just the one being edited. How do I make so that only the one being edited gets focused?
You can render a template with data, which is basically just an object passed to it when inserted in to a page.
The data could simply be the key to use in the Session for editState.
eg, render the template with Template.form({editStateKey:'editState-topForm'})
you could make a handlebars helper eg,
Handlebars.registerHelper('formWithOptions',
function(editStateKey){
return Template.form({editStateKey:editStateKey})
});
then insert it in your template with
{{{formWithOptions 'editState-topForm'}}} (note the triple {, })
Next, change references from Session.x('editState') to Session.x(this.editStateKey)/ Session.x(this.data.editStateKey)
Template.form.helpers({
editState: function() {
return Session.get(this.editStateKey);
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get(this.data.editStateKey)) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, null);
},
});
Note: if you are using iron-router it has additional api's for passing data to templates.
Note2: In meteor 1.0 there is supposed to be better support for writing your own widgets. Which should allow better control over this sort of thing.
As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.
Alternative approaches
1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:
'click .js-edit-action': function(event, t) {
var $this = $(event.currentTarget),
container = $this.parents('.phenom-comment');
// open and focus
container.addClass('editing');
container.find('textarea').focus();
},
'click .js-confirm-delete-action': function(event, t) {
CardComments.remove(this._id);
},
2 ReactiveVar scoped to template instance
if (Meteor.isClient) {
Template.hello.created = function () {
// counter starts at 0
this.counter = new ReactiveVar(0);
};
Template.hello.helpers({
counter: function () {
return Template.instance().counter.get();
}
});
Template.hello.events({
'click button': function (event, template) {
// increment the counter when button is clicked
template.counter.set(template.counter.get() + 1);
}
});
}
http://meteorcapture.com/a-look-at-local-template-state/
3 Iron-Router's state variables
Get
Router.route('/posts/:_id', {name: 'post'});
PostController = RouteController.extend({
action: function () {
// set the reactive state variable "postId" with a value
// of the id from our url
this.state.set('postId', this.params._id);
this.render();
}
});
Set
Template.Post.helpers({
postId: function () {
var controller = Iron.controller();
// reactively return the value of postId
return controller.state.get('postId');
}
});
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#setting-reactive-state-variables
4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.
5 update the data context
Session is often the worse choice in my opinion. Also I don't personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as "Flow".
In our application we use a general function to create jQuery dialogs which contain module-specific content. The custom dialog consists of 3 buttons (Cancel, Save, Apply). Apply does the same as Save but also closes the dialog.
Many modules are still using a custom post instead of an ajax-post. For this reason I'm looking to overwrite/redefine the buttons which are on a specific dialog.
So far I've got the buttons, but I'm unable to do something with them. Is it possible to get the buttons from a dialog (yes, I know) but apply a different function to them?
My code so far:
function OverrideDialogButtonCallbacks(sDialogInstance) {
oButtons = $( '#dialog' ).dialog( 'option', 'buttons' );
console.log(oButtons); // logs the buttons correctly
if(sDialogInstance == 'TestInstance') {
oButtons.Save = function() {
alert('A new callback has been assigned.');
// code for ajax-post will come here.
}
}
}
$('#dialog').dialog({
'buttons' : {
'Save' : {
id:"btn-save", // provide the id, if you want to apply a callback based on id selector
click: function() {
//
},
},
}
});
Did you try this? to override button's callback based on the need.
No need to re-assign at all. Try this.
function OverrideDialogButtonCallbacks(dialogSelector) {
var button = $(dialogSelector + " ~ .ui-dialog-buttonpane")
.find("button:contains('Save')");
button.unbind("click").on("click", function() {
alert("save overriden!");
});
}
Call it like OverrideDialogButtonCallbacks("#dialog");
Working fiddle: http://jsfiddle.net/codovations/yzfVT/
You can get the buttons using $(..).dialog('option', 'buttons'). This returns an array of objects that you can then rewire by searching through them and adjusting the click event:
// Rewire the callback for the first button
var buttons = $('#dialog').dialog('option', 'buttons');
buttons[0].click = function() { alert('Click rewired!'); };
See this fiddle for an example: http://jsfiddle.net/z4TTH/2/
If necessary, you can check the text of the button using button[i].text.
UPDATE:
The buttons option can be one of two forms, one is an array as described above, the other is an object where each property is the name of the button. To rewire the click event in this instance it's necessary to update the buttons option in the dialog:
// Rewire the callback for the OK button
var buttons = $('#dialog').dialog('option', 'buttons');
buttons.Ok = function() { alert('Click rewired!'); };
$('#dialog').dialog('option', 'buttons', buttons);
See this fiddle: http://jsfiddle.net/z4TTH/3/
Can you try binding your new function code with Click event of Save?
if(sDialogInstance == 'TestInstance') {
$('#'+savebtn_id).click(function() {
alert('A new callback has been assigned.');
// code for ajax-post will come here.
});
}