Grid Drag & Drop: Suppress for some records (groups) - javascript

Hy there,
is it possible to control which records can be dragged and where they can be dropped (suppress drag-operation either right from the beginning or in the middle during hovering)?
What i need in detail is the following:
I'm having a grid with some groups (lets say male & female) and only want to activate the d&d inside group 'female' which means 2 things:
1.) I started dragging a record from group 'female' (Lisa). As soon as the drag is outside the group 'female' (above group 'male'...) it should display an error-state like when dragging outside the bounds of the grid:
2.) Starting to drag an item from group 'male' should either not be possible at all (just don't show the d&d panel) or show the error-state like mentioned above right from the beginning and never change to "correct"-state.
Thanks,
mike

After some digging around in the sources of ext i just found a solution which works but isn't perfect at all:
The "drop-allowed-indication" can be handled by the underlying DropZone which is created in onViewRender of the treeviewdragdrop-plugin. This is not documented but can be seen in the source-code of the plugin.
Everything that needs to be done (at least for this example) is to override/extend the onNodeOver- & onContainerOver-method of the DropZone to return the appropriate css-class for the drop-not-allowed- or drop-allowed-indication.
Ext.override(Ext.view.DropZone, {
onNodeOver: function(nodeData, source, e, data) {
if (data && data.records && data.records[0]) {
// The check should be better specified, e.g. a
// female with the name 'Malena' would be recognized as male!
if (nodeData.innerHTML.indexOf(data.records[0].get('sex')) < 0) {
return this.dropNotAllowed;
}
}
return this.callOverridden([nodeData, source, e, data]);
},
onContainerOver: function(source, e, data) {
return this.dropNotAllowed;
}
});
Working example: http://jsfiddle.net/suamikim/auXdQ/
There are a few things i don't like about this solution:
The override changes (per definition...) the behaviour of all DropZones in my application. How can i only override/extend the specific DropZone of one grid?I've tried the following:
Add an interceptor to the dropZone after the gridview has been rendered: http://jsfiddle.net/suamikim/uv8tX/
At first this seems to work because it shows the correct drop-allowed-indication but it drops the record even if the indicator shows that it's not allowed (it always shows the "green line"...)
Define a new dnd-plugin which extends the treeviewdragdrop-plugin and just override the onNodeOver-method of the dropZone after it's creation: http://jsfiddle.net/suamikim/5v67W/
This kind of does the opposite from the interception-method. It also shows the correct indication but it never shows the "green line" and won't allow the drop anywhere...
The class i'm overriding (Ext.view.DropZone) is marked private in the documentation with a note that it shouldn't be used directly...
I would really appreciate some comments on those 2 issues and maybe even some better solutions!
Thanks, mik
Edit:
I adjusted the version in which i defined a new dnd-plugin which extended the original gridviewdragdrop-plugin. The "magic" was to also extend gridviewdropzone and extend the onNodeOver-method instead of just overriding it.
This needs to be done because the original onNodeOver-method which is now called by callParent handles the "green line" and finally allows the drop.
The only thing my extended gridviewdragdrop-plugin does now is to create a instance of the new dropzone-class instead of the standard gridviewdropzone in the onViewRender-method.
This seems like a reasonable way so far:
// Extend the treeview dropzone
Ext.define('ExtendedGridViewDropZone', {
extend: 'Ext.grid.ViewDropZone',
onNodeOver: function(nodeData, source, e, data) {
if (data && data.records && data.records[0]) {
// The check should be specified, e.g. a female with the name 'Malena' would be recognized as male!
if (nodeData.innerHTML.indexOf(data.records[0].get('sex')) < 0) {
return this.dropNotAllowed;
}
}
return this.callParent(arguments);
},
onContainerOver: function(source, e, data) {
return this.dropNotAllowed;
}
});
Ext.define('ExtendedGridDnD', {
extend: 'Ext.grid.plugin.DragDrop',
alias: 'plugin.extendeddnd',
onViewRender: function(view) {
this.callParent(arguments);
// Create a instance of ExtendedGridViewDropZone instead of Ext.grid.ViewDropZone
this.dropZone = Ext.create('ExtendedGridViewDropZone', {
view: view,
ddGroup: this.dropGroup || this.ddGroup
});
}
});
Working example: http://jsfiddle.net/5v67W/1/
Nonetheless I'd still appreciate different approaches because it still feels like it could be done easier...

You can do it like this for Ext 5 and 6
On your treepanel definition:
listeners: {
viewready : 'onViewReady'
}
on your ViewController:
onViewReady : function (tree) {
var view = tree.getView(),
dd = view.findPlugin('treeviewdragdrop'),
rec;
dd.dropZone.onNodeOver = function (data, e) {
rec = view.getRecord(e.getTarget(view.itemSelector));
return rec.get('customProperty') === 'someValue' ? this.dropAllowed : this.dropNotAllowed;
}
},
reference https://www.sencha.com/forum/showthread.php?282685
https://www.sencha.com/blog/declarative-listeners-in-ext-js-5/

Related

Can I protect custom events from "preventDefault"?

We have a Stencil web component that renders a user dialog. It consists of an "outerComponent" and an "innerComponent".
The outer one cares about dealing with the browser (props, load stuff from cookies etc.) and the inner one renders the actual HTML and lets the user operate it.
(Actually there are more components used inside, from a different project for the UI components such as checkbox, button etc. But I don't think that's relevant here.)
When a checkbox, button etc. is clicked in <inner-component> an onclick-handler within the component is called that executes some UI logic (e.g. set the "checked" property) and then emits a custom event, e.g.:
#Event() checkboxToggleModalEvent: EventEmitter<OptionType>;
...
<checkbox-comp fid="...">
<input type="checkbox" checked={optionCheckbox.userSelection} onClick={this.handleCheckbox} />
...
</checkbox-comp>
...
private handleCheckbox(event: Event) {
const checkboxElement: HTMLInputElement = event.target as HTMLInputElement;
...
const selection: OptionType = { name: indexId, userSelection };
this.checkboxToggleModalEvent.emit(selection);
}
Now, in <outer-component> this event is listened for and the handler cares for the "technical" logic:
#Listen("checkboxToggleModalEvent")
checkboxToggleModalEventHandler(event) {
LogService.log.debug(event);
... some technical logic
}
This works fine in most cases. Now we have an integration on one site, where the events apparently do not get emitted correctly or somehow lost in the middle.
The UI logic is executed normally but the handler in outerComponent never gets called.
I was able to find the piece of code from an integrated library that causes the problem (sorry for pasting the whole function!):
// From the imported library on customer website:
function(t, exports) {
try {
var e = new window.CustomEvent("test");
if (e.preventDefault(),
!0 !== e.defaultPrevented)
throw new Error("Could not prevent default")
} catch (t) {
var n = function(t, e) {
var n, r;
return e = e || {
bubbles: !1,
cancelable: !1,
detail: void 0
},
n = document.createEvent("CustomEvent"),
n.initCustomEvent(t, e.bubbles, e.cancelable, e.detail),
r = n.preventDefault,
n.preventDefault = function() {
r.call(this);
try {
Object.defineProperty(this, "defaultPrevented", {
get: function() {
return !0
}
})
} catch (t) {
this.defaultPrevented = !0
}
}
,
n
};
n.prototype = window.Event.prototype,
window.CustomEvent = n
}
}
If I remove this, everything works as expected.
Now, I'm wondering if we can somehow "protect" our events from being intercepted like this as the component should really work in any case (that's why we chose this technology).
But I also would be very grateful for any hints to what might actually cause the problem.
Thanks a lot!!
n.prototype = window.Event.prototype,
window.CustomEvent = n
Looks like they overloaded CustomEvent and injected their own code.
This is the drawback of using 3rd party software.
In this case, only way to get around this is to get in early, and overload CustomEvent yourself.
But you then have the challenge of making their code work; because they did this overloading for a reason.
What is the 3rd party software? Publically shame them.
For those who want to try overloading, execute this early:
window.customeElements.define = () => {}

How can I specify step definition file for each feature file in cucumber-js?

My Goal
I am trying to create a scalable structure of features and step definitions for a large application and my first shot was trying to link step_definition files to features so that I could use the same step pattern for different step definitions.
My Code
I show my current example:
My folder structure:
/features/sample.feature
/features/example.feature
/features/step_definitions/sample_steps.js
/features/step_definitions/example_steps.js
/features/step_definitions/common/common_steps.js
In my sample.feature I have:
Scenario: Launching Cucumber
Given I have some step definitions
When I check some step definition with parameter "any"
Then I should see all green "sample"
In my example.feature I have:
Scenario: Launching Cucumber
Given I have some step definitions
When I check some step definition with parameter "any"
Then I should see all green "example"
The Given and When steps are defined at /common/common_steps.js file and works fine.
The Then step is defined both to sample_steps.js and example_steps.js but differently.
In my sample_steps.js I have:
Then('I should see all green {stringInDoubleQuotes}', (arg) => {
if (arg !== 'sample') {
throw 'I should see all green when the argument is "sample"';
}
return;
});
And, finally, in my example_steps.js I have:
Then('I should see all green {stringInDoubleQuotes}', (arg) => {
if (arg !== 'example') {
throw 'I should see all green when the argument is "example"';
}
return;
});
The Error
My main goal is to have all green here, but of course, it doesn't work and I get this obviouly error:
Multiple step definitions match:
I should see all green {stringInDoubleQuotes} - features\step_definitions\example_steps.js:6
I should see all green {stringInDoubleQuotes} - features\step_definitions\sample_steps.js:6
Cucumber-JVM
I know that in cucumber-jvm we can specify a glue attribute that links features and step_definitions and it's exactly what I'm looking for, but in cucumber-js. Example in Java:
#RunWith(Cucumber.class)
#Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.sample" } )
public class SampleFeature{
}
#RunWith(Cucumber.class)
#Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.example" } )
public class ExampleFeature{
}
Finally
How can I achieve the same as cucumbr-jvm using cucumber-js?
Can't you just do this:
Then('I should see all green "sample"', () => {
return;
});
Then('I should see all green "example"', () => {
return;
});
The thing with step definitions in a larger codebase is that many steps are very similar. i.e: I navigate to "page" - you could put the code in a common place, and have the urls elsewhere.
Personally, I would do this:
Then('I should see all green "(.*)"', (arg) => {
// DO SOME COMMON STUFF
arg = arg.toLowerCase();
if(arg === "sample"){
// DO SAMPLE OWN STUFF
}else if(arg === "example"){
// DO EXAMPLE STUFF
}
// CARRY ON WITH COMMON STUFF
});
But this could get quite long. If they do completely different things, go with the first option, if they do pretty much the same thing, use the second one.
It may actually be that you only need to do this:
Then('I should see all green "(.*)"', (arg) => {
// DO SOME COMMON STUFF
arg = arg.toLowerCase();
var result = runTheseSteps(arg);
// CARRY ON WITH COMMON STUFF
});
EDIT
I've found a way that could do it. It would be a generic function, which isn't part of your requirements (I'm sorry), but I believe that it may be the only way to get it to work.
In your hooks:
this.Before(function (scenario, callback) {
var tags = [];
for(var i=0;i<scenario.getTags().length; i++ ){
tags[i] = scenario.getTags()[i].getName();
}
global.scenarioTags = tags.join(", ");
callback();
});
In your step definition:
this.Given(/^I should see all "(.*)"$/, function (arg) {
if (scenarioTags.includes("#sample") != null) {
// DO THE SAMPLE STUFF
} else {
// DO THE EXAMPLE STUFF
}
});
While it leaves your step definition generic, it means you can put anything into the double quotes, and as long as you tag your scenario, the right code will run.
#sample
Scenario: Launching Cucumber
Given I have some step definitions
When I check some step definition with parameter "any"
Then I should see all green "sample"
#example
Scenario: Launching Cucumber
Given I have some step definitions
When I check some step definition with parameter "any"
Then I should see all green "example"

How can I replace an existing Kendo UI widget?

I'm trying to extend an existing KendoUI widget (autocomplete). Since our application is already using a lot of instances of the autocomplete widget, I donĀ“t want to create a new widget which extends the current one but rather replace the existing one.
I already found this topic: kendo-ui autocomplete extend, but unfortunately it points creating a new one.
I tried the following code:
var plg = kendo.ui.AutoComplete.extend({
options: {
name: 'AutoCompleteMyOne'
},
init: function (_element, _options)
{
kendo.ui.AutoComplete.fn.init.call(this, _element, _options);
/*...*/
}
});
kendo.ui.plugin(plg);
The point is the name-attribute of the options. If the name is only "AutoComplete" the initialization does not work anymore: This line ends in an endlessloop:
kendo.ui.AutoComplete.fn.init.call(this, _element, _options);
How can I call the base-initialization or is it really overwritten?
If you replace the auto-complete widget, then your code is effectively calling its own init method recursively.
So you need to store the existing method and call that one, e.g. like this:
var plg = (function (init) {
return kendo.ui.AutoComplete.extend({
options: {
name: 'AutoComplete'
},
init: function (_element, _options) {
// modify the placeholder
_options.placeholder += " (custom)";
init.call(this, _element, _options);
/*...*/
}
});
})(kendo.ui.AutoComplete.fn.init);
kendo.ui.plugin(plg);
(demo)

Referencing a parent object in callback functions with jQuery

I've a page that is generated dynamically, and that includes certain number (user-dynamically-defined) of advanced scatter plot charts. I intend to create a JavaScript object which defines the scatter plot itself, i.e. which takes some parameters, some data, and some container ID, and which will create the various elements needed to obtain the visualisation: canvas elements, toolbar, etc.. To do so, I started with the following (simplified) class:
(function () {
if (!this.namespace) { this.namespace = {};}
this._instances = { index: 0 };
this.namespace.ScatterPlot = function (containerId, file, options) {
_instances.index ++;
this.id = this.containerId+"-"+_instances.index ;
this.containerId = containerId ;
_instances [this.id] = this;
// ... Do stuffs with file and options ...
// Initialize elements once the DOM is ready
$(this.updateDOM);
}
namespace.ScatterPlot.prototype = {
updateDOM: function() {
$("<canvas>")
.click(clickCallback)
.appendTo("#"+this.containerId);
//(...)
},
clickCallback: function() {
alert("Some click: "+this.id);
}
}
})();
Each object can be created with:
var v1 = new namespace.ScatterPlot("container1", "foo", "foo");
var v2 = new namespace.ScatterPlot("container2", "foo", "foo");
There are two problems here: (1) in updateDOM, 'this' does not make reference to my initial ScatterPlot object, which means that this example will never work, and (2) similarly, the clickCallback will not be able reference the scatterplot with 'this' either.
I'm new to javascript, and I'm still struggeling to understand the logic of OO programming in javascript, so the question is: I'm I taking the wrong direction here ? After some digging, I could roughly achieve what I wanted by passing this to updateDOM:
$(this.updateDOM(this)); // This blows my eyes but does the trick, at least partially
updateDOM: function(that) {
$("<canvas>")
.click(that.clickCallback)
.appendTo("#"+that.containerId);
//(...)
},
clickCallback: function() {
// Not working either... Should pass 'that' to the function too
alert("Some click: "+this.id);
}
But I don't feel this patters to be very elegant... And the problem is not fixed either regarding the click callback.
Thoughts ?
Have a look at MDN's introduction to the this keyword.
The standard ways of dealing with that issue are using a that variable - not as an argument, but in a separate function:
var that = this;
$(function() {
that.updateDOM();
});
// or
$(this.getClickCallback());
...
namespace.ScatterPlot.prototype.getClickCallback = function() {
var that = this;
return function clickCallback(e) {
alert("Some click: "+that.id);
};
};
Alternatively, you can always use .bind() (or $.proxy for older browsers) which do quite what the second example does in a more generic way:
$(this.clickCallback.bind(this));

XUL/Thunderbird: startEditing return

I'm playing with the thunderbird codebase, the aim being to implement inline contact editing. The current code catches the Click event on a XUL tree, and if it's a double click (events.detail == 2), it open the profile editor. I modified it so as to start editing the current treeCell, and I did add editable=true to the corresponding XUL document. The updated code reads
var orow = {}, ocolumn = {}, opart = {};
gAbResultsTree.treeBoxObject.getCellAt(event.clientX, event.clientY,
orow, ocolumn, opart);
var row = orow.value, column = ocolumn.value.index;
if (row == -1)
return;
if (event.detail == 2)
gAbResultsTree.startEditing(row, column);
Unfortunately, when the code reaches the startEditing part, it returns
Error: uncaught exception: [Exception... "Component returned failure code: 0x80004001 (NS_ERROR_NOT_IMPLEMENTED) [nsITreeView.isEditable]" nsresult: "0x80004001 (NS_ERROR_NOT_IMPLEMENTED)" location: "JS frame :: chrome://global/content/bindings/tree.xml :: startEditing :: line 337" data: no]
I'm pretty much lost here. Could someone with more XUL experience help?
Thanks!
I was trying to do something similar and I have same problem.
Wrapper with original abview set as __proto__ with functions overriden works fine until it is set as abResultsTree's view.
I've finally found (I hope) an elegant solution.
function MyAbView() {
this.originalAbViewInstance = this.originalAbViewFactory.createInstance(null, Ci.nsIAbView);
if (!this.proxiesGenerated) {
// find out which interfaces are implemented by original instance, their property proxies will be generated later
for (var ifName in Ci) {
if (Ci[ifName] instanceof Ci.nsIJSID && this.originalAbViewInstance instanceof Ci[ifName]) {
MyAbView.prototype.supportedInterfaces.push(Ci[ifName]);
}
}
function generatePropertyProxy(name) {
Object.defineProperty(MyAbView.prototype, name, {
get: function() {
return this.originalAbViewInstance[name];
},
set: function(val) {
this.originalAbViewInstance[name] = val;
},
enumerable: true
});
}
for (var prop in this.originalAbViewInstance) {
if (this[prop] == undefined) {
generatePropertyProxy(prop);
}
}
MyAbView.prototype.proxiesGenerated = true;
} else {
for each (var interface in this.supportedInterfaces) {
this.originalAbViewInstance.QueryInterface(interface);
}
}
}
MyAbView.prototype = {
classID: null,
_xpcom_factory: {
createInstance: function(outer, iid) {
return new MyAbView().QueryInterface(iid);
}
},
QueryInterface: function(aIID) {
for each (var interface in this.supportedInterfaces) {
if (interface.equals(aIID)) {
return this;
}
}
throw Components.results.NS_ERROR_NO_INTERFACE;
},
originalAbViewFactory: null,
originalAbViewInstance: null,
proxiesGenerated: false,
supportedInterfaces: [],
// any overriden functions come here
};
It's implemented as a component to replace the original abview, but it might be modified to just create a wrapper.
The <tree> widget uses an nsITreeView object to retrieve or manipulate data that needs to be displayed. There are predefined nsITreeView implementations reading data from the DOM or RDF datasources but one can choose to use his own tree view. Thunderbird's address book chooses the latter:
gAbView = Components.classes["#mozilla.org/addressbook/abview;1"]
.createInstance(Components.interfaces.nsIAbView);
...
gAbResultsTree.treeBoxObject.view =
gAbView.QueryInterface(Components.interfaces.nsITreeView);
Unfortunately for you, the component in question is implemented in C++, in the file nsAbView.cpp. This means that changing it without recompiling Thunderbird isn't possible. And the existing component doesn't implement isEditable() and setCellText() methods that would be required to edit tree cells.
If you don't want to mess with C++ yet, you could wrap that component in your own object. Something like this:
gAbView = Components.classes["#mozilla.org/addressbook/abview;1"]
.createInstance(Components.interfaces.nsIAbView);
gAbViewWrapper = {
__proto__: gAbView,
QueryInterface: function(iid)
{
gAbView.QueryInterface(iid);
return this;
},
isEditable: function(row, col)
{
// Do something here
},
setCellText: function(row, col, value)
{
// Do something here
}
};
...
gAbResultsTree.treeBoxObject.view =
gAbViewWrapper.QueryInterface(Components.interfaces.nsITreeView);
Method isEditable() should check again whether this particular cell is editable - even if the column is editable, individual cells don't have to be. And setCellText() should store the new value for the cell.

Categories

Resources