Finding the deepest nested components in DOM tree - reactjs

I am trying to implement a keybinding mixin which let me write something like
createClass({
...
keybindings: function() {
return {
'escape' : function(event) { this._handleEscapeKey(); },
'enter' : function(event) { this._handleEnterKey(); },
'up' : function(event) { this._handleUpKey(); },
'down' : function(event) { this._handleDownKey(); },
'left' : function(event) { this._handleLeftKey(); },
'right' : function(event) { this._handleRightKey(); },
};
},
In my component. However I have problems when multiple components include the mixin. I need a way make the event listeners on the most deeply nested components in the DOM tree get precedence over its ancestors.
This is what I got so far, any ideas/suggestions/feedback is much appreciated
mixin:
KeybindingsMixin = {
modifiers: {
shift: 16,
ctrl: 17,
alt: 18,
meta: 91
},
keyCodes: {
escape : 27,
up : 38,
down : 40,
left : 37,
right : 39,
enter : 13,
shift : 16,
ctrl : 17,
alt : 18,
meta : 91,
s : 83,
},
singleModifier: {
shift: {
keyCode : 16,
shift : true,
ctrl : false,
alt : false,
meta : false
}
},
componentDidMount: function() {
if (this.__keybindings !== undefined) {
var keybindings = this.getAllKeybindings();
this.__keybindings = _.merge(this.__keybindings, keybindings);
$(window).on('keydown', this.__matchEvent);
}
},
componentWillUnmount: function() {
if (this.__keybindings !== undefined) {
var keybindings = _.keys(this.getAllKeybindings());
this.__keybindings = _.omit(this.__keybindings, keybindings);
$(window).off('keydown', this.__matchEvent);
}
},
childContextTypes: {
__keybindings: React.PropTypes.object
},
contextTypes: {
__keybindings: React.PropTypes.object
},
getAllKeybindings: function() {
return this.__getKeybindings();
},
getChildContext: function() {
return {
__keybindings: this.__getKeybindings()
};
},
__getKeybindings: function() {
var keybindings = Object.getPrototypeOf(this).keybindings();
this.__keybindings = this.__keybindings || (this.context && this.context.__keybindings) || keybindings || {};
return this.__keybindings;
},
__parseKeybindingString: function(binding) {
var tokens = binding.split(' ');
var modifiers = _.keys(this.modifiers);
var bindings = _.keys(this.keyCodes);
var parsedEvent = {
keyCode: 0,
alt: false,
ctrl: false,
shift: false,
meta: false
};
_.each(tokens, function(token) {
if (_.includes(modifiers, token)) {
parsedEvent[token] = true;
} else if (_.includes(bindings, token)) {
parsedEvent.keyCode = this.keyCodes[token];
}
}, this);
return parsedEvent;
},
__keybindingSpecified: function(event) {
},
__matchEvent: function(event) {
var eventMatcher = {
keyCode: event.keyCode,
alt: event.altKey,
ctrl: event.ctrlKey,
shift: event.shiftKey,
meta: event.metaKey,
};
var keybindings = this.__keybindings;
_.forOwn(keybindings, function(action, binding) {
var parsedEvent = this.__parseKeybindingString(binding);
if (_.isEqual(eventMatcher, parsedEvent)) {
event.preventDefault();
return action.call(this, event);
}
}, this);
return;
},
};

If by "deeply nested components in the DOM tree get precedence over its ancestors" you mean that the event should only trigger on the most deeply nested component and not bubble up to the components further up, you should call event.stopPropagation(). This cancels the bubbling, and the components further up won't get the event.
EDIT
Ah, sorry, I thought you used Reacts event system which allows stopPropagation even if they only attach one event handler to the root element. What you can do is something similar to what React does, in that for each element that uses the the mixin, you attach some property to that DOM node to say that it's listening to the event. Then when the event comes in, you check event.target and see if it has that property. If it doesn't, you check event.target.parentElement and recursive upwards until you find it. And as soon as you find that element, you stop going upwards to simulate the effect of stopPropagation.

Related

ExtJS Maximum call stack size exceeded when reordering tree

I'm reordering the hierarchy of a tree with drag and drop. After moving multiple nodes I get the error Uncaught RangeError: Maximum call stack size exceeded. The error appears in NodeInterface.js. The updateInfo function crashes in the following line
for (i = 0; i < childCount; i++) {
children[i].updateInfo(commit, childInfo);
}
What could cause this problem?
The following code shows how I implemnted the drag and drop and reordering in ExtJS 6.5.2. Maybe you can find what's causing the problem.
Plugin
Ext.define('test.component.plugin.TreeDragger', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.treedrag',
mixins: ['Ext.mixin.Observable'],
constructor: function (config) {
this.mixins.observable.constructor.call(this, config);
},
init: function (component) {
var me = this;
this.source = new Ext.drag.Source({
element: component.element,
handle: '.x-gridrow',
constrain: {
element: true,
vertical: true
},
describe: function (info) {
var row = Ext.Component.from(info.eventTarget, component);
info.row = row;
info.record = row.getRecord();
},
proxy: {
type: 'placeholder',
getElement: function (info) {
console.log('proxy: getElement');
var el = Ext.getBody().createChild({
style: 'padding: 10px; width: 100px; border: 1px solid gray; color: red;',
});
el.show().update(info.record.get('description'));
return el;
}
},
// autoDestroy: false,
listeners: {
scope: me,
beforedragstart: me.makeRelayer('beforedragstart'),
dragstart: me.makeRelayer('dragstart'),
dragmove: me.makeRelayer('dragmove'),
dragend: me.makeRelayer('dragend')
}
});
},
disable: function () {
this.source.disable();
},
enable: function () {
this.source.enable();
},
doDestroy: function () {
Ext.destroy(this.source);
this.callParent();
},
makeRelayer: function (name) {
var me = this;
return function (source, info) {
return me.fireEvent(name, me, info);
};
}
});
Tree
xtype: 'tree',
hideHeaders: true,
plugins: {
treedrag: {
type: 'treedrag',
listeners: {
beforedragstart: function (plugin, info) {
console.log('listeners: beforedragstart');
}
}
}
},
columns: [{
xtype: 'treecolumn',
flex: 1,
}
]
Controller
afterLoadApportionmentObjectsForTree: function (succes) {
if (succes) {
tree = this.getView().down('tree');
if (tree) {
tree.expandAll();
tree.updateHideHeaders(tree.getHideHeaders());
var store = tree.getStore();
store.remoteFilter = false;
store.filterer = 'bottomup';
this.createDropTargets();
}
}
},
createDropTargets: function () {
var me = this,
rows = tree.innerItems;
Ext.each(rows, function (el) {
var target = new Ext.drag.Target({
element: el.element,
listeners: {
scope: me,
drop: me.onDrop
}
});
});
},
onDrop: function (target, info, eOpts) {
var source = info.record,
row = Ext.Component.from(target.getElement(), tree),
destination = row.getRecord(),
parentNode = source.parentNode;
destination.appendChild(source);
destination.expand();
if (!parentNode.hasChildNodes()) {
parentNode.set('leaf', true);
}
}
Edit
It seems that updateInfo is called recursive, but I can't figure out why or how I could prevent it.
I could find the mistake by myself. It was possible to drag nodes on their own children which relates to an recursive adding and removing of the same nodes over and over again. To prevent the user from doing this, I added an listener for the beforeDrop event to my Ext.drag.Target. There it returns false, if the target node is the same as the source node and it returns false if the target node is a child node of the source node.
onBeforeDrop: function (target, info, eOpts) {
var source = info.record,
row = Ext.Component.from(target.getElement(), tree),
destination = row.getRecord();
if (source == destination) {
return false;
}
if (source.findChild('number', destination.get('number'), true) != null) {
return false;
}
return true;
}
I also used the beforedragstart event to prevent moving the root node.
Maybe this is helpful for someone else.

ExtJS Drag and Drop in tree on modern toolkit

Actually I'm going to implement a tree view, where the user should have the option to reorder the structure with drag and drop. Actually I can't figure out how to enable drag and drop. I found a lot of examples using the 'treeviewdragdrop' plugin, which is just working with the classic toolkit.
The following Code made me move the first node but not more.
this.toParentSource = new Ext.drag.Source({
element: this.getView().element.down('.x-gridcell'),
constrain: {
element: this.getView().body,
vertical: true
}
});
Can you help me with this problem? I'm using ExtJS 6.5.2 modern toolkit.
This is how I enabled drag and drop for trees in modern Ext JS:
First I've written a plugin which creates the sources that should be draggable.
Plugin
Ext.define('test.component.plugin.TreeDragger', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.treedrag',
mixins: ['Ext.mixin.Observable'],
constructor: function (config) {
this.mixins.observable.constructor.call(this, config);
},
init: function (component) {
var me = this;
this.source = new Ext.drag.Source({
element: component.element,
handle: '.x-gridrow',
constrain: {
element: true,
vertical: true
},
describe: function (info) {
var row = Ext.Component.from(info.eventTarget, component);
info.row = row;
info.record = row.getRecord();
},
proxy: {
type: 'placeholder',
getElement: function (info) {
console.log('proxy: getElement');
var el = Ext.getBody().createChild({
style: 'padding: 10px; width: 100px; border: 1px solid gray; color: red;',
});
el.show().update(info.record.get('description'));
return el;
}
},
// autoDestroy: false,
listeners: {
scope: me,
beforedragstart: me.makeRelayer('beforedragstart'),
dragstart: me.makeRelayer('dragstart'),
dragmove: me.makeRelayer('dragmove'),
dragend: me.makeRelayer('dragend')
}
});
},
disable: function () {
this.source.disable();
},
enable: function () {
this.source.enable();
},
doDestroy: function () {
Ext.destroy(this.source);
this.callParent();
},
makeRelayer: function (name) {
var me = this;
return function (source, info) {
return me.fireEvent(name, me, info);
};
}
});
Next I used this plugin inside my tree.
Tree
xtype: 'tree',
hideHeaders: true,
plugins: {
treedrag: {
type: 'treedrag',
listeners: {
beforedragstart: function (plugin, info) {
// logic to identify the root and prevent it from being moved
console.log('listeners: beforedragstart');
}
}
}
},
columns: [{
xtype: 'treecolumn',
flex: 1,
}
]
Then I defined the drop targets inside the controller.
Controller
afterLoadApportionmentObjectsForTree: function (succes) {
if (succes) {
tree = this.getView().down('tree');
if (tree) {
tree.expandAll();
tree.updateHideHeaders(tree.getHideHeaders());
var store = tree.getStore();
store.remoteFilter = false;
store.filterer = 'bottomup';
this.createDropTargets();
}
}
},
createDropTargets: function () {
var me = this,
rows = tree.innerItems;
Ext.each(rows, function (el) {
var target = new Ext.drag.Target({
element: el.element,
listeners: {
scope: me,
drop: me.onDrop,
beforeDrop: me.onBeforeDrop
}
});
});
},
onDrop: function (target, info, eOpts) {
var source = info.record,
row = Ext.Component.from(target.getElement(), tree),
destination = row.getRecord(),
parentNode = source.parentNode;
destination.appendChild(source);
destination.expand();
if (!parentNode.hasChildNodes()) {
parentNode.set('leaf', true);
}
},
onBeforeDrop: function (target, info, eOpts) {
var source = info.record,
row = Ext.Component.from(target.getElement(), tree),
destination = row.getRecord();
// prevent the user to drop the node on itself
// this would lead to an error caused by recursive method calls
if (source == destination) {
return false;
}
// prevent the user to drop a node on it's children
// this would lead to an error caused by recursive method calls
if (source.findChild('number', destination.get('number'), true) != null) {
return false;
}
return true;
}

How to update event source object to particular event source in full calendar dynamically

Hello I have loaded one calendar using below code.
dataFactory.httpRequest('getCalanderTaskData','GET').then(function(data) {
$scope.taskCalanderDailyTask = data.dailyTask;
$scope.taskCalanderDueTask = data.dueTask;
$scope.fullCalendarEventSources = {
dailyTask : {
events:$scope.taskCalanderDailyTask,
textColor : '#000',
backgroundColor: '#FFD000',
},
dueTask : {
events:$scope.taskCalanderDueTask,
textColor : '#fff',
backgroundColor: '#da1445',
}
};
$('#fullcalendar').fullCalendar({
eventClick: function(calEvent, jsEvent, view) {
},
eventMouseover: function(calEvent, jsEvent, view) {
var tooltip = '<div class="tooltipevent" style="color:#fff;width:100px;height:50px;background:#125688;position:absolute;z-index:10001;">' + calEvent.title + '</div>';
var $tooltip = $(tooltip).appendTo('body');
$(this).mouseover(function(e) {
$(this).css('z-index', 10000);
$tooltip.fadeIn('500');
$tooltip.fadeTo('10', 1.9);
}).mousemove(function(e) {
$tooltip.css('top', e.pageY + 10);
$tooltip.css('left', e.pageX + 20);
});
},
eventMouseout: function(calEvent, jsEvent) {
$(this).css('z-index', 8);
$('.tooltipevent').remove();
},
eventLimit: 3,
eventSources: [$scope.fullCalendarEventSources.dailyTask, $scope.fullCalendarEventSources.dueTask],
});
});
Above code load first time calendar now when any new task going to added I want to refresh calendar event source without reload page.
Below is the code for saveTask Inside that I have written code for dynamic event source which also remove source and added source but it not update newly added tasks.
$scope.saveTask = function(){
$scope.form.taskLeadId = $scope.leadId;
$scope.form.taskId = $scope.editTaskId;
if($scope.form.taskLeadId != "undefined" && $scope.form.taskLeadId != "")
{
dataFactory.httpRequest('taskCreate','POST',{},$scope.form).then(function(data) {
if(data.status == 1)
{
alertify.notify(data.message, 'success', 5, function(){});
$scope.addTask.$setPristine();
$scope.addTask.$setUntouched();
$scope.form = {};
$(".modal").modal("hide");
getDailyTaskData();
getCalanderTaskData();
console.log("updateed=="+$scope.taskCalanderDailyTask);
$scope.fullCalendarEventSources = {
dailyTask : {
events:$scope.taskCalanderDailyTask,
textColor : '#000',
backgroundColor: '#FFD000',
},
dueTask : {
events:$scope.taskCalanderDueTask,
textColor : '#fff',
backgroundColor: '#da1445',
}
};
$timeout(function () {
$('#fullcalendar').fullCalendar('removeEventSource',$scope.fullCalendarEventSources.dailyTask);
console.log("updateed=="+$scope.taskCalanderDailyTask);
$('#fullcalendar').fullCalendar('addEventSource',$scope.fullCalendarEventSources.dailyTask);
$('#fullcalendar').fullCalendar('refetchEventSources',$scope.fullCalendarEventSources.dailyTask);
$('#fullcalendar').fullCalendar('rerenderEvents');
$('#fullcalendar').fullCalendar('refetchEvents');
$('#fullcalendar').fullCalendar('refresh');
$('#fullcalendar').fullCalendar('updateEvents',$scope.fullCalendarEventSources.dailyTask);
$('#fullcalendar').fullCalendar('rerenderEvents');
$('#fullcalendar').fullCalendar('refetchEvents');
$('#fullcalendar').fullCalendar('refresh');
console.log("after update==");
});
//alertify.alert('Task', 'Congratulations your task is created successfully!');
}
else
{
if($scope.editTask = "edit")
{
alertify.alert('Task Update','Nothing is updated');
}
else
{
alertify.alert('Error', data.message,function(){ alertify.error(data.message)});
$(".modal").modal("hide");
}
}
});
}
else
{
alertify.alert('Error', 'There must be something went wrong please try again later');
$(".modal").modal("hide");
}
}
Any one please help on this.

Mobile list collapsible items

I am trying to create a list of collapsible containers. The container should collapse and expand. I have created an example list item renderer.
qx.Class.define("mb.ui.list.QuotaWeekListRenderer",
{
extend : qx.ui.mobile.list.renderer.Default,
members :
{
__collapsible : null,
__weeksContainer : null,
_init : function()
{
this.ignoreBase;
this.__collapsible = this._createCollapsible();
this.add(this.__collapsible);
},
setTitle : function(title)
{
this.ignoreBase;
if (title && title.translate)
{
this.__collapsible.setTitle(title.translate());
}
else
{
this.__collapsible.setTitle(title);
}
},
addWeek : function(value)
{
var label = new qx.ui.mobile.basic.Label(value);
this.__collapsible.add(label);
},
_createCollapsible : function()
{
return new qx.ui.mobile.container.Collapsible();
},
// overridden
reset : function()
{
this.ignoreBase;
this.setTitle("");
this.__collapsible.getContent().removeAll();
}
}
});
var page = new qx.ui.mobile.page.NavigationPage();
page.setTitle("List");
page.addListener("initialize", function()
{
// List creation
var list = new qx.ui.mobile.list.List({
configureItem : function(item, data, row)
{
item.setTitle("Week " + parseInt(data.weekNo));
for (var i = 0, l = data.weekDates.length; i < l; i++)
{
item.addWeek(data.weekDates[i]);
}
},
createItemRenderer : function()
{
return new mb.ui.list.QuotaWeekListRenderer();
}
});
// Create the data
var data = [{title: "title1", weekNo: 1, weekDates : ["1/2/2014", "2/2/2014"]},
{title: "title2", weekNo : 2, weekDates : ["2/3/2015", "9/3/2015"]}];
list.setModel(new qx.data.Array(data));
page.getContent().add(list);
},this);
this.getManager().addDetail(page);
page.show();
The above can be run in Playground
My problem is that the items don't expand on 'tap'. Listener toggles the "collapsed" property of the container, but it has no effect on the DOM element. Any ideas how to fix it?
something like this, maybe?
Playground example

Angular with Coffeescript: why my method are executed?

I'm an angular beginner, and coming from Ruby I choose to use Coffescript instead of JS. I'm using ng-classify to define my controller, services and Factory with Coffeescript classes, but I cannot understand what is wrong.
I have my code in this [github repo], but I try to explain here my issue.
I have this controller
class Setting extends Controller
constructor: (#DataService,$log) ->
#examType = #DataService.getObject('setting_examtype') || { checked: false }
#settingList = #DataService.getObject('setting_list') || [
{ text: 'Dai precedenza a domande sbagliate', checked: false },
{ text: 'Dai precedenza a domande mai fatte', checked: false },
{ text: 'Mostra subito la soluzione', checked: false }
]
#questionPossibility = [10,20,30,40,50]
#questionNumber = #DataService.get('question_number') || 30
return
examTypeChecked: () =>
#DataService.setObject('setting_examtype',#examType)
console.log 'examTypeChecked'
return
settingListChecked: () =>
console.log 'settingListChecked'
#DataService.setObject('setting_list',#settingList)
return
questionNumberChecked: () =>
console.log 'questionNumberChecked'
#DataService.set('question_number',#questionNumber)
return
The compiled version is:
(function() {
var Setting,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Setting = (function() {
function Setting(DataService, $log) {
this.DataService = DataService;
this.questionNumberChecked = __bind(this.questionNumberChecked, this);
this.settingListChecked = __bind(this.settingListChecked, this);
this.examTypeChecked = __bind(this.examTypeChecked, this);
this.examType = this.DataService.getObject('setting_examtype') || {
checked: false
};
this.settingList = this.DataService.getObject('setting_list') || [
{
text: 'Dai precedenza a domande sbagliate',
checked: false
}, {
text: 'Dai precedenza a domande mai fatte',
checked: false
}, {
text: 'Mostra subito la soluzione',
checked: false
}
];
this.questionPossibility = [10, 20, 30, 40, 50];
this.questionNumber = this.DataService.get('question_number') || 30;
return;
}
Setting.prototype.examTypeChecked = function() {
this.DataService.setObject('setting_examtype', this.examType);
console.log('examTypeChecked');
};
Setting.prototype.settingListChecked = function() {
console.log('settingListChecked');
this.DataService.setObject('setting_list', this.settingList);
};
Setting.prototype.questionNumberChecked = function() {
console.log('questionNumberChecked');
this.DataService.set('question_number', this.questionNumber);
};
return Setting;
})();
angular.module('app').controller('settingController', ['DataService', '$log', Setting]);
}).call(this);
As you can see I insert some log statement, and from the console I understand that all my methods are executed. Why? Why examTypeChecked is called?
I call it only if someone use a toggle..
<ion-toggle ng-model="setting.examType" ng-checked="setting.examTypeChecked()" toggle-class="toggle-calm" ng-true-value="oltre" ng-false-value="entro">Tipo di esame</ion-toggle>
You got it wrong way, your code is fine, use of code is not what you expected
<ion-toggle ng-model="setting.examType" ng-checked="setting.examTypeChecked()" toggle-class="toggle-calm" ng-true-value="oltre" ng-false-value="entro">Tipo di esame</ion-toggle>
setting.examTypeChecked() will be called every time $digest() process is triggered, and it's triggered with each change of model, by $scope.apply(), $scope.digest(), $timeout() and few more

Resources