Gridstack widgets can't be moved after new widgets are added - javascript

Application made with RactiveJS, Redux and Gridstack.
When new widgets are added, all is fine and widgets are movable/resizable as well. But when I delete all widgets and add, for example, two new, then:
widgets can't be moved and can't be resized. As in picture:
when try to delete widget it disappear, but other widgets change their position.
jsFiddle is provided as follows
You can see that right widget is moved to the right side, but handle stays where it is. So why is such behavior and how to deal with that?
RactiveJS application have three components
DashboardComponent
WidgetGridComponent
WidgetComponent
Code provided as follows:
var Widget = Ractive.extend({
isolated: false, // To pass events to WidgetGrid component (makeWidget, removeWidget, etc.)
template: '#widgetTemplate',
components: {},
oninit: function() {
// Load data to widget
},
oncomplete: function() {
this.drawChart();
},
drawChart: function() {
var self = this;
function exampleData() {
return [{
"label": "One",
"value": 29.765957771107
},
{
"label": "Two",
"value": 0
},
{
"label": "Three",
"value": 32.807804682612
},
{
"label": "Four",
"value": 196.45946739256
},
{
"label": "Five",
"value": 0.19434030906893
},
{
"label": "Six",
"value": 98.079782601442
},
{
"label": "Seven",
"value": 13.925743130903
},
{
"label": "Eight",
"value": 5.1387322875705
}
];
}
nv.addGraph(function() {
var chart = nv.models.pieChart()
.x(function(d) {
return d.label
})
.y(function(d) {
return d.value
})
.showLabels(true);
d3.select("#widget" + self.get("id") + " svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
},
data: function() {
return {
id: null,
x: null,
y: null,
width: null,
height: null,
}
}
});
var WidgetGrid = Ractive.extend({
// isolated:false,
// twoway:false,
template: '#widgetGridTemplate',
components: {
Widget: Widget,
},
onrender: function() {
// Init gridstack instance
this.bindGridstack();
},
deleteWidget: function(id) {
Action.deleteWidget(id);
},
removeWidget: function(id) {
$(".grid-stack").data("gridstack").removeWidget("#widget" + id);
},
createWidget: function(id) {
$(".grid-stack").data("gridstack").makeWidget("#widget" + id);
},
updateWidgetSize: function(id, width, height) {
Action.updateWidgetSize(id, width, height);
},
updateWidgetPosition: function(id, x, y) {
Action.updateWidgetPosition(id, x, y);
},
bindGridstack: function() {
var self = this;
var options = {
animate: true,
auto: false, // if false gridstack will not initialize existing items (default: true)
float: true, // enable floating widgets (default: false)
disableOneColumnMode: true,
width: 10, // amount of columns (default: 12)
height: 10, // maximum rows amount. Default is 0 which means no maximum rows
// height: 10,
// cellHeight: 80,
disableResize: false,
disableDrag: false,
verticalMargin: 0,
resizable: {
handles: 'se'
}
};
var grid = $(".grid-stack").gridstack(options);
// On user ends resizing
grid.on('gsresizestop', function(event, elem) {
var $el = $(elem);
var node = $el.data('_gridstack_node');
var id = node.el.attr("id").replace("widget", "");
self.updateWidgetSize(id, node.width, node.height, node.el.css("width"), node.el.css("height"));
});
// On user ends dragging
grid.on('dragstop', function(event, ui) {
var $el = $(event.target);
var node = $el.data('_gridstack_node');
var id = $el.attr("id").replace("ar-widget", "");
self.updateWidgetPosition(id, node.x, node.y);
});
},
data: function() {
return {
widgets: [],
}
}
});
var Dashboard = Ractive.extend({
el: '#dashboard', //document.body,
template: '#dashboardTemplate',
isolated: true,
append: false,
oninit: function() {
this.on("add", function() {
Action.addWidget()
});
},
components: {
WidgetGrid: WidgetGrid,
},
data: function() {
return {
store: {}
}
}
});

There's several problems in there:
detach is not called when unrendering a component. It's only called when ractive.detach() is called. Your grid was not aware of the widget's removal. After Ractive unrendered the widget elements, this caused the grid to act weird.
On the flip side, grid.removeWidget() removes the widget from the DOM. After a state change, Ractive will still try to unrender. But since the element is no longer there and because Ractive wasn't aware of the widget's removal, it will cause a double removal.
After a state change, the data for the widget is no longer present. id is already undefined. You can no longer use id when you handle teardown, unrender nor destruct events. You'll have to disconnect a widget from the grid earlier, preferably before the state change.
Ideally, you should just let Ractive do the rendering/unrendering of elements and only inform gridstack about making/disconnecting widgets from the grid. You're already doing this on render by using makeWidget instead of addWidget. For removal, you simply need to do the same thing. removeWidget accepts a second argument which, if false, will disassociate a widget from the grid but not remove the element from the DOM. This leaves unrendering to Ractive after the state change.
Here's an example: https://jsfiddle.net/fm133mk6/

Related

How can i get the data-id of a element?

I want to get the data-id of the element selected in pop-up.
i have to show the selection in the grid, whose solution you have provided. but for Database storage i need the ID value of the selection... how can i get and bind the ID in the grid.???
<script type="text/javascript">
$(function () {
// declaration
$("#lognForm").ejDialog(
{
enableModal: true,
enableResize: false,
width: 291,
close: "onDialogClose",
containment: ".cols-sample-area",
showFooter: true,
footerTemplateId: "sample"
});
$("#defaultlistbox").ejListView({ dataSource:ej.DataManager({
url: "http://js.syncfusion.com/ejServices/Wcf/Northwind.svc/", crossDomain: true
}),
query: ej.Query().from("Suppliers").select("SupplierID", "ContactName"),
fieldSettings: { text: "ContactName"},mouseUp: "onmouseup",height:"400px" ,enableCheckMark: true,
enableFiltering: true,
});
$("#btnOpen").ejButton({ size: "medium", "click": "onOpen", type: "button", height: 30, width: 172 });
$("#Grid").ejGrid({
columns: [
{ field: "title", headerText: "ListviewData", width: 80 },
]
});
$("#btn1").ejButton({ size: "medium", "click": "onbtnOpen", type: "button", height: 30, width: 172 });
});
function onOpen() {
$("#btnOpen").hide();
$("#lognForm").ejDialog("open");
}
function onbtnOpen(){
$("#lognForm").ejDialog("close");
}
function onDialogClose(args) {
$("#btnOpen").show();
}
function onmouseup(e) {
var selections = $('#defaultlistbox').ejListView("getCheckedItems");
var items = [];
$(selections).each(function () {
var $this = $(this);
var item = { title: $this.find("span").html() };
items.push(item);
});
if (selections.length > 0) {
var obj = $("#Grid").ejGrid("instance");
obj.setModel({ dataSource: items })
obj.refreshContent();
}
else{
var obj = $("#Grid").ejGrid("instance");
obj.dataSource([]); }
}
</script>
<li class="e-user-select e-list e-list-check e-state-default" data-id="2">
If you have a reference to that javascript DOM object, simply look for the dataset property. It's a hashmap containing string keys, that are on your HTML element prefixed with data.
For example, you'll find data-id on your object as myObject.dataset["id"].

How to increase JSTree drop area

I am using the jstree to build up tasks on a calender. I have set the div on which the tree is initialised to a min height of 50px, which means when I am dragging the first node onto an empty tree I can drop it anywhere within that 50px which is great.
However if I then want to drop another node (onto main tree branch) I now much drop it almost on top of the previous node, which means the user needs to be very exact about where they are dropping
Here is the div container

here is the code where the JSTree is attached:
dayTree = moment.format('DD-MM-YYYY');
$('#' + dayTree).jstree({
"core": {
"id": moment.format('DD-MM-YYYY'),
"animation": 150,
'check_callback': function (operation, node, node_parent, node_position, more) {
return true; //allow all other operations
},
"themes": {"stripes": false},
"data": {
async: false,
"url": APIURL+"/shiftassignments?asg_date=" + moment.format('YYYY-MM-DD'),
"dataType": "json",
"success": function (data) {
}
}
},
"rules": {
droppable: ["tree-drop"],
multiple: true,
deletable: "all",
draggable: "all"
},
"contextmenu": {
'items' : getContextMenu
},
"types": {
"#": {
"valid_children": ["SH_LS", "SH_AS", "TO_LS", "TO_AS"]
},
"SH_AS": {
"valid_children": ["ST_LS", "ST_AS"]
},
"TO_AS" : {
"valid_children" : ["ST_LS", "ST_AS"]
},
"TO_LS" : {
"valid_children" : [""]
},
"SH_LS": {
"valid_children": [""]
},
"ST_LS": {
"valid_children": [""]
},
"ST_AS": {
"valid_children": [""]
}
},
"dnd": {
open_timeout: 100,
always_copy: true,
large_drop_target: true
},
"plugins": ["contextmenu", "dnd", "types"]
})
attached is a screenshot to explain!
I guess effectively I would like to increase the drop-zone and have a 'snap-to' effect to the main '#' node. Maybe its not possible?
Screenshot
I would listen to events on the div container for the second tree and if a dragged item is then dropped on that container and second tree has only the root, then just add it there.
The code could look something like below. See demo - Fiddle.
var overSecondTree = false;
$(document).on('dnd_move.vakata', function(e, data) {
// change icon to `allow drop` if from first tree and over second tree with a single root node
if ( $('#tree2 li').length === 1 && overSecondTree ) {
// change icon to `allow drop`
$('#vakata-dnd').find('i').removeClass('jstree-er').addClass('jstree-ok');
} else {
// change icon to `restrict drop`
$('#vakata-dnd').find('i').removeClass('jstree-ok').addClass('jstree-er');
}
});
$(document).on('dnd_stop.vakata', function(e, data) {
// allow drop to tree2 only if single root node
if ( $('#tree2 li').length === 1 && overSecondTree) {
$("#tree2").jstree().create_node('#rootTree2', data.element.text);
}
})
$("#tree2").mouseenter(function(event){
overSecondTree = true;
if ( $('#tree2 li').length === 1 ) {
$('#rootTree2').addClass('highlighted');
}
})
$("#tree2").mouseleave(function(event){
overSecondTree = false;
$('#rootTree2').removeClass('highlighted');
})

Javascript hard-coded object works as parameter, but not reference to global object

I'm running into an odd issue with JQuery Flot.
I had been running
var plot = null;
function initPlot () {
plot = $.plot("#graph", myData, { /* my options here */ });
}
and everything worked fine.
I moved my options data into a variable within the scope of the function
var plot = null;
function initPlot () {
var myOptions = { /* my options here */ };
plot = $.plot("#graph", myData, myOptions);
}
and again, everything works fine.
Then I moved the variable to a broader scope (so I could reference it in other functions).
var plot = null;
var myOptions = { /* my options here */ };
function initPlot () {
plot = $.plot("#graph", myData, myOptions);
}
Suddenly it won't plot correctly. What's even more confusing to me is that it gets the values of most of the options--the graph appears, just not the data.
Using Chrome's debugger, I can see that myOptions has the correct value at the time I call $.plot(). I also tried setting a local variable o = myOptions and calling $.plot() with o instead of myOptions, but this, too, fails to plot the data.
What's causing this and how do I fix it?
I should note that I'm using Flot.Grow and Flot.Stack, but disabling these does not change the result.
EDIT (again)
Per the comments, here is literally the state of my code:
<body>
<div id="graph" style="width: 100%; height: 800px;"></div>
<script>
var idMap = {};
var yesData = [];
var noData = [];
var incData = [];
var voteData = [{ label: "Yes votes", data: yesData },
{ label: "No votes", data: noData },
{ label: "Not met quorum", data: incData }];
var ticks = [];
var plot = null;
var myOptions = {
series: {
//stack: true,
lines: {
show: false,
fill: true,
steps: false,
},
bars: {
show: true,
barWidth: .8,
horizontal: true,
},
//grow: {
// active: false,
// steps: 20,
// stepDirection: "up",
// stepMode: "linear",
// valueIndex: 0,
// growings: [{
// valueIndex: 0,
// }]
//},
},
yaxis: {
min: -.2,
max: 10, //dictLen(idMap),
//ticks: ,
//tickLength: 0,
},
xaxis: {
min: 0,
max: 200,
},
grid: {
hoverable: true,
},
};
function dictLen(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
function setDataPoint(val) {
var total = val.r["yes"] + val.r["no"];
if (total < val.q) {
incData.push([total, idMap[val.id]]);
}
else {
yesData.push([val.r["yes"], idMap[val.id]]);
noData.push([val.r["no"], idMap[val.id]]);
}
}
function initData() {
$.getJSON('/Vote/results', function (data) {
$.each(data, function (key, val) {
idMap[val.id] = key;
ticks.push([key + .4, val.nm]);
setDataPoint(val);
});
initPlot();
});
}
function initPlot() {
plot = $.plot("#graph", voteData, myOptions);
}
$(document).ready(function () {
initData();
});
</script>
</body>
Turns out I'm a bit of an idiot.
The issue was that I was calculating myOptions.yaxis.max before idMap was populated. Then, while I was waiting for an answer here, I did some refactoring on the database side, which resulted in an empty dataset.
When I fixed the db and set myOptions to only static values, it worked fine.

Developing custom kendo ui widget

How can I develop a custom widget behaving like this sample:
http://jsfiddle.net/anilca/u2HF7/
Here is my kickstart reading, but I could not find out how to define the templates of dropdownlists and link them together by change events.
(function($) {
var kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget;
var Editable = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(this, element, options);
that._create();
},
options: {
name: "Editable",
value: " ",
editable: false
},
_create: function() {
var that = this;
// create dropdowns from templates and append them to DOM
},
_templates: {
dropDown: "?"
}
});
ui.plugin(Editable);
})(jQuery);
Following the blog you linked, simply do what he did - define your templates then create the drop-downs in your _create() function. It is not necessary to use the kendo.template() function on each drop down list, as it does not bind objects as well as you think it would. Instead, the key is to use kendo.bind() and pass in the options as your view model.
Here is a JsBin working example.
// listen for doc ready because this is js bin and my code is not really "in" the HTML
// where I can control it.
$(function() {
// wrap the widget in a closure. Not necessary in doc ready, but a good practice
(function($) {
var kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget,
periods = [{ text: "PERIOD: YEAR", value: "YEAR" }, { text: "PERIOD: QUARTER", value: "QUARTER" }],
quarters = [{ text: "1. Quarter", value: 1 }, { text: "2. Quarter", value: 2 }, { text: "3. Quarter", value: 3 }, { text: "4. Quarter", value: 4 }],
years = [2011, 2012, 2013, 2014];
var LinkedDropDowns = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._create();
},
options: {
name: "LinkedDropDowns",
period: "YEAR",
periods: periods,
year: 2011,
years: years,
yearVisible: true,
quarter: 1,
quarters: quarters,
quarterVisible: false,
onPeriodChange: function(e) {
switch(e.sender.value()) {
case "YEAR":
this.set("yearVisible", true);
this.set("quarterVisible", false);
break;
case "QUARTER":
this.set("yearVisible", true);
this.set("quarterVisible", true);
break;
default:
break;
}
},
onYearChange: function(e) {
alert(e.sender.value());
},
onQuarterChange: function(e) {
alert(e.sender.value());
}
},
_create: function() {
var that = this;
// create dropdowns from templates and append them to DOM
that.periodDropDown = $(that._templates.periodDropDown);
that.yearDropDown = $(that._templates.yearDropDown);
that.quarterDropDown = $(that._templates.quarterDropDown);
// append all elements to the DOM
that.element.append(that.periodDropDown)
.append(that.yearDropDown)
.append(that.quarterDropDown);
kendo.bind(that.element, that.options);
},
_templates: {
periodDropDown: "<input id='period' data-role='dropdownlist' data-text-field='text' data-value-field='value' data-bind='value: period, source: periods, events: { change: onPeriodChange }' />",
yearDropDown: "<input id='year' data-role='dropdownlist' data-bind='visible: yearVisible, value: year, source: years, events: { change: onYearChange }' style='width: 90px' />",
quarterDropDown: "<input id='quarter' data-role='dropdownlist' data-text-field='text' data-value-field='value' data-bind='visible: quarterVisible, value: quarter, source: quarters, events: { change: onQuarterChange }' style='width: 110px' />"
}
});
ui.plugin(LinkedDropDowns);
})(jQuery);
$('#dropdowns').kendoLinkedDropDowns();
});

Dojo grid : loose selection after update

I have am currently using Dojo EnhancedGrid with Pagination, filtering and cell edition.
The problem is that in one page, I need to update another value when a cell is edited. When I update this value, I loose the cell selected so I need to click on the next cell to select it and modify it.
It is so not possible to do Enter / edit / enter / down / enter / edit (Excel like edition).
Here is a part of my code :
var store = new dojo.data.ItemFileWriteStore({'data':data});
var grid = new dojox.grid.EnhancedGrid({
id: 'grid',
store: store,
structure: structure,
columnReordering: true,
autoHeight: true,
autoWidth: true,
initialWidth: '100%',
escapeHTMLInData: false,
plugins: {
pagination: {
pageSizes: ["10", "25", "50", "All"],
description: true,
sizeSwitch: true,
pageStepper: true,
gotoButton: true,
maxPageStep: 4,
position: "bottom"
},
filter : {}
},
onStartEdit: function(inCell, inRowIndex)
{
item = grid.selection.getSelected()[0];
currentVal = item[inCell['field']][0];
},
doApplyCellEdit: function(inValue, inRowIndex, inAttrName) {
if(inValue != currentVal){
[...]
$.ajax(url, data, {
success:function(data, textStatus) {
val = parseInt(data["info"]);
if(!isNaN(val)) {
grid.store.setValue(item, 'info', val);
grid.update();
} else {
grid.store.setValue(item, 'info', 0);
grid.update();
}
}
});
}
}
});
dojo.byId("gridDiv").appendChild(grid.domNode);
grid.startup();
Do you see any solution to handle this ?
I too use an enhanced dojo grid, where I had a similar problem. I used this to reload the data right:
require(["dijit/registry"],function(registry) {
var grid = registry.byId('grid');
grid._lastScrollTop=grid.scrollTop;
grid._refresh();
});
With this you always should get the last row you manipulated, and eventually also the last selected one... .
After many research, I have found the following solution :
$.ajax(url, data, {
success:function(data, textStatus) {
val = parseInt(data["info"]);
if(!isNaN(val)) {
grid.store.setValue(item, 'info', val);
grid.update();
window.setTimeout(function() {
grid.focus.next();
}, 10);
} else {
grid.store.setValue(item, 'info', 0);
grid.update();
window.setTimeout(function() {
grid.focus.next();
}, 10);
}
}
});
The timer is needed because there is a short delay before grid update the thus loosing the focus.

Categories