I have multiple interconnected Dojo HorizontalSlider. In the onChange event of the sliders the values of the other sliders are updated. Changing the value programmatically fires the onChange event of the corresponding slider, which again fires its own onChange event and overrides the values.
I do need to set intermediateChanges = true, which makes the priorityChange flag useless in this case. Also i tried setting up a variable that checks whether one slider is active, but due to timing issues this does not work as well.
Simplified example with two sliders:
var sliding = false;
var slider1 = new HorizontalSlider({
value: 70,
minimum: 0,
maximum: 100,
discreteValues: 101,
intermediateChanges: true,
onChange: function() {
if (sliding == false) {
sliding = true;
new_value = [...]
slider2.set("value", new_value, false);
};
sliding = false;
}
}, "slider1");
var slider2 = new HorizontalSlider({
value: 30,
minimum: 0,
maximum: 100,
discreteValues: 101,
intermediateChanges: true,
onChange: function() {
if (sliding == false) {
sliding = true;
new_value = [...]
slider1.set("value", new_value, false);
};
sliding = false;
}
}, "slider2");
slider1.startup();
slider2.startup();
I also found this example, which seems to address the same problem with Dojo Select: Dojo Select onChange event firing when changing value programatically
How to prevent firing the onChange event of a slider when changing its value programmatically?
Thank you!
Maybe a bit ugly, but you could do this:
var sliding = false;
var slider1 = new HorizontalSlider({
value: 70,
minimum: 0,
maximum: 100,
discreteValues: 101,
intermediateChanges: true,
onChange: function() {
if (sliding == false) {
sliding = true;
new_value = [...]
slider2.set("intermediateChanges", false);
slider2.set("value", new_value, false);
slider2.set("intermediateChanges", true);
};
sliding = false;
}
}, "slider1");
var slider2 = new HorizontalSlider({
value: 30,
minimum: 0,
maximum: 100,
discreteValues: 101,
intermediateChanges: true,
onChange: function() {
if (sliding == false) {
sliding = true;
new_value = [...]
slider1.set("intermediateChanges", false);
slider1.set("value", new_value, false);
slider1.set("intermediateChanges", true);
};
sliding = false;
}
}, "slider2");
slider1.startup();
slider2.startup();
Related
I have created a codepen that uses jquery ui droppable(for drag/drop), jsPlumb (for flowcharting) and Panzoom (panning and zooming) to create a flowchart builder. You could drag the list items from the draggable container (1st column) to the flowchart (2nd column) and then connect the items using the dots to create a flowchart. The #flowchart is a Panzoom target with both pan and zoom enabled. This all works fine.
However, I would like to have the #flowchart div always span the whole area of the flowchart-wrapper i.e. the #flowchart should be an infinite canvas that supports panning, zooming and is a droppable container.
It should have the same effect as flowchart-builder-demo. The canvas there is infinite where you can drag and drop items (Questions, Actions, Outputs) from the right column.
Any pointers on how to achieve this (like the relevant events or multiple panzoom elements and/or css changes) would be greatly appreciated.
const BG_SRC_TGT = "#2C7BE5";
const HEX_SRC_ENDPOINT = BG_SRC_TGT;
const HEX_TGT_ENDPOINT = BG_SRC_TGT;
const HEX_ENDPOINT_HOVER = "#fd7e14";
const HEX_CONNECTOR = "#39afd1";
const HEX_CONNECTOR_HOVER = "#fd7e14";
const connectorPaintStyle = {
strokeWidth: 2,
stroke: HEX_CONNECTOR,
joinstyle: "round",
outlineStroke: "white",
outlineWidth: 1
},
connectorHoverStyle = {
strokeWidth: 3,
stroke: HEX_CONNECTOR_HOVER,
outlineWidth: 2,
outlineStroke: "white"
},
endpointHoverStyle = {
fill: HEX_ENDPOINT_HOVER,
stroke: HEX_ENDPOINT_HOVER
},
sourceEndpoint = {
endpoint: "Dot",
paintStyle: {
stroke: HEX_SRC_ENDPOINT,
fill: "transparent",
radius: 4,
strokeWidth: 3
},
isSource: true,
connector: ["Flowchart", { stub: [40, 60], gap: 8, cornerRadius: 5, alwaysRespectStubs: true }],
connectorStyle: connectorPaintStyle,
hoverPaintStyle: endpointHoverStyle,
connectorHoverStyle: connectorHoverStyle,
dragOptions: {},
overlays: [
["Label", {
location: [0.5, 1.5],
label: "Drag",
cssClass: "endpointSourceLabel",
visible: false
}]
]
},
targetEndpoint = {
endpoint: "Dot",
paintStyle: {
fill: HEX_TGT_ENDPOINT,
radius: 5
},
hoverPaintStyle: endpointHoverStyle,
maxConnections: -1,
dropOptions: { hoverClass: "hover", activeClass: "active" },
isTarget: true,
overlays: [
["Label", { location: [0.5, -0.5], label: "Drop", cssClass: "endpointTargetLabel", visible: false }]
]
};
const getUniqueId = () => Math.random().toString(36).substring(2, 8);
// Setup jquery ui draggable, droppable
$("li.list-group-item").draggable({
helper: "clone",
zIndex: 100,
scroll: false,
start: function (event, ui) {
var width = event.target.getBoundingClientRect().width;
$(ui.helper).css({
'width': Math.ceil(width)
});
}
});
$('#flowchart').droppable({
hoverClass: "drop-hover",
tolerance: "pointer",
drop: function (event, ui) {
var helper = $(ui.helper);
var fieldId = getUniqueId();
var offset = $(this).offset(),
x = event.pageX - offset.left,
y = event.pageY - offset.top;
helper.find('div.field').clone(false)
.animate({ 'min-height': '40px', width: '180px' })
.css({ position: 'absolute', left: x, top: y })
.attr('id', fieldId)
.appendTo($(this)).fadeIn('fast', function () {
var field = $("#" + fieldId);
jsPlumbInstance.draggable(field, {
containment: "parent",
scroll: true,
grid: [5, 5],
stop: function (event, ui) {
}
});
field.addClass('panzoom-exclude');
var bottomEndpoints = ["BottomCenter"];
var topEndPoints = ["TopCenter"];
addEndpoints(fieldId, bottomEndpoints, topEndPoints);
jsPlumbInstance.revalidate(fieldId);
});
}
});
const addEndpoints = (toId, sourceAnchors, targetAnchors) => {
for (var i = 0; i < sourceAnchors.length; i++) {
var sourceUUID = toId + sourceAnchors[i];
jsPlumbInstance.addEndpoint(toId, sourceEndpoint, { anchor: sourceAnchors[i], uuid: sourceUUID });
}
for (var j = 0; j < targetAnchors.length; j++) {
var targetUUID = toId + targetAnchors[j];
jsPlumbInstance.addEndpoint(toId, targetEndpoint, { anchor: targetAnchors[j], uuid: targetUUID });
}
$('.jtk-endpoint').addClass('panzoom-exclude');
}
// Setup jsPlumbInstance
var jsPlumbInstance = jsPlumb.getInstance({
DragOptions: { cursor: 'pointer', zIndex: 12000 },
ConnectionOverlays: [
["Arrow", { location: 1 }],
["Label", {
location: 0.1,
id: "label",
cssClass: "aLabel"
}]
],
Container: 'flowchart'
});
// Setup Panzoom
const elem = document.getElementById('flowchart');
const panzoom = Panzoom(elem, {
excludeClass: 'panzoom-exclude',
canvas: true
});
const parent = elem.parentElement;
parent.addEventListener('wheel', panzoom.zoomWithWheel);
I've just been working on the exact same issue and came across this as the only answer
Implementing pan and zoom in jsPlumb
The PanZoom used looks to be quite old - but the idea was the same, use the JQuery Draggable plugin for the movable elements, instead of the in-built JsPlumb one. This allows the elements to move out of bounds.
The below draggable function fixed it for me using the PanZoom library.
var that = this;
var currentScale = 1;
var element = $('.element');
element.draggable({
start: function (e) {
//we need current scale factor to adjust coordinates of dragging element
currentScale = that.panzoom.getScale();
$(this).css("cursor", "move");
that.panzoom.setOptions({ disablePan: true });
},
drag: function (e, ui) {
ui.position.left = ui.position.left / currentScale;
ui.position.top = ui.position.top / currentScale;
if ($(this).hasClass("jtk-connected")) {
that.jsPlumbInstance.repaintEverything();
}
},
stop: function (e, ui) {
var nodeId = $(this).attr('id');
that.jsPlumbInstance.repaintEverything();
$(this).css("cursor", "");
that.panzoom.setOptions({ disablePan: false });
}
});
I'm not sure if redrawing everything on drag is that efficient - so maybe just redraw both the connecting elements.
The API documentation (http://js.cytoscape.org/#extensions/api) says that cytoscape( type, name, extension ) whould register an extension.
It worked in v2.4 but doesn't work anymore in v2.6.
What is the right way to do it now ?
EDIT:
Here is what I do
;(function($$){ 'use strict';
var defaults = {
fit: true, // whether to fit the viewport to the graph
padding: 30, // the padding on fit
startAngle: 3/2 * Math.PI, // the position of the first node
counterclockwise: false, // whether the layout should go counterclockwise/anticlockwise (true) or clockwise (false)
minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment)
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
height: undefined, // height of layout area (overrides container height)
width: undefined, // width of layout area (overrides container width)
concentric: function(node){ // returns numeric value for each node, placing higher nodes in levels towards the centre
return node.degree();
},
levelWidth: function(nodes){ // the variation of concentric values in each level
return nodes.maxDegree() / 4;
},
animate: false, // whether to transition the node positions
animationDuration: 500, // duration of animation in ms if enabled
ready: undefined, // callback on layoutready
stop: undefined // callback on layoutstop
};
function ConcentricLayout( options ){
console.log('INIT');
this.options = $$.util.extend({}, defaults, options);
}
ConcentricLayout.prototype.run = function(){
console.log('RUNNING');
// Run code
};
$$('layout', 'customconcentric', ConcentricLayout);
})( cytoscape );
And how I use it
var cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
elements: p,
layout: {
name: 'null',
stop: function() {
cy.elements('node.group').forEach(function( ele ){
var eles = ele.children();
eles.layout({
name: 'customgrid',
fit: false,
avoidOverlapPadding: 0,
columns: 2
});
});
cy.elements('node.machine').forEach(function( ele ){
var elesleft = ele.children('node.mod');
var elesright = ele.children('node.group');
var descendants = elesright.descendants();
if (elesleft.length > 0) {
var relpos = getRelativePositions(descendants);
elesright.forEach(function( ele ){
ele.relativePosition('x', 200);
});
setRelativePositions(relpos, cy);
elesleft.layout({
name: 'customgrid',
fit: false,
avoidOverlapPadding: 0,
columns: 1
});
}
});
cy.elements('node.machine, node.env').layout({
name: 'customconcentric',
fit: true
});
}
}
});
With 2.4.9 I see this in my browser's javascript console.
cytoscape.layout.custom.js:41 INIT
cytoscape.layout.custom.js:46 RUNNING
With 2.6.2, nothing happens.
The registration API is fine. You're relying on a private object that is not accessible:
this.options = $$.util.extend({}, defaults, options);
Only reference public APIs in the docs if you want your code to be compatible with newer versions: http://js.cytoscape.org
Use a proper debugger, like in Chrome. Your debugger should show you an error message when you reference non-existent objects or otherwise cause exceptions.
I'm trying to add a class "disabled" to the pager if the slider has only 1 slide showing, and nothing to actually slide.
I'm sliding a div, not a list.
It's just a basic div:
<div class="bxslider2">
<div class="wrap">
...
</div>
</div>
Here is the jquery for the slider:
$('.bxslider2').bxSlider({
mode: 'horizontal',
speed: '180',
pagerType:'full',
pager:'true',
captions: false
});
I would like to not show the pager if there is only 1 slide showing.
Thanks for any help!
Justin
I faced the same trouble and here is my solution: we should count how many child elements we have under .bxslider2 block
$(".bxslider2>.wrap").length
and if there is only one, then set option to 'false', otherwise to 'true'.
$('.bxslider2').bxSlider({
mode: 'horizontal',
speed: '180',
pagerType:'full',
pager: ($(".bxslider2>.wrap").length > 1) ? true: false,
captions: false
});
Hope it will helps.
I use css to achieve this.
.bx-pager-item:first-of-type:last-of-type {
display: none
}
Here is a solution if you have more than one bxslider on the page.
$('.bxslider').each(function() {
var options = {
mode: 'fade',
};
if ($(this).find('li').length === 1) {
options.pager = false;
}
$(this).bxSlider(options);
});
Goes through each slider and finds if it has only one li. If so, add pager: false to the object passed to bxSlider.
a nice way to solve this thing is to reload the object like that
and change the controls per number of the items :
var slideQty = $('.bxslider').bxSlider({
minSlides: 1,
maxSlides: 3,
slideWidth: 110,
slideMargin: 0,
adaptiveHeight: true,
pager: false,
moveSlides: 1,
onSlideAfter: function ($slideElement, oldIndex, newIndex) { NextSlide(oldIndex, newIndex); }
});
var count = slideQty.getSlideCount();
if (count < 7) {
slideQty.reloadSlider({
controls : false,
minSlides: 1,
maxSlides: 3,
slideWidth: 110,
slideMargin: 0,
adaptiveHeight: true,
pager: false,
moveSlides: 1,
onSlideAfter: function ($slideElement, oldIndex, newIndex) { NextSlide(oldIndex, newIndex); }
});
}
return false;
Try this it works for me!!
var slider = $('#slider').bxSlider();
$('.bx-next, .bx-prev, .bx-pager a').click(function(){
// time to wait (in ms)
var wait = 1000;
setTimeout(function(){
slider.startAuto();
}, wait);
});
I'm trying to animate the height of a dataview, but it's currently just sliding the panel around the viewport instead of keeping it in place and changing it's height. The code is as follows:
Ext.Anim.run(el, 'slide', {
from: { height: height },
to: { height: newHeight },
out: false,
direction: 'up',
easing: 'ease-out',
duration: 1000
});
For instance, height=200, newHeight=100 will result in the dataview dropping immediately so that it's top is at 200px below the viewport, and then animating back to the top of the viewport.
How can I get it to change the height? Thanks.
Try using Ext.Animator.run instead:
Ext.Animator.run({
element: dataview.element,
duration: 500,
easing: 'ease-in',
preserveEndState: true,
from: {
height: dataview.element.getHeight()
},
to: {
height: 100
}
});
And within a full example:
Ext.application({
name: 'Sencha',
launch: function() {
var dataview = Ext.create('Ext.DataView', {
fullscreen: true,
style: 'background:red',
store: {
fields: ['text'],
data: [
{ text: 'one' },
{ text: 'two' },
{ text: 'three' }
]
},
itemTpl: '{text}'
});
Ext.Viewport.add({
xtype: 'button',
docked: 'top',
handler: function() {
Ext.Animator.run({
element: dataview.element,
duration: 500,
easing: 'ease-in',
preserveEndState: true,
to: {
height: 100
},
from: {
height: dataview.element.getHeight()
}
});
}
});
}
});
Since I can't add comments, I'll have to put this as a separate answer. I just wanted to add to what rdougan said and show how you can catch the animation end event. I find it's necessary in the above situation because Sencha Touch's component.getTop/Left/Height/Width() functions return incorrect values after an animation such as the one shown.
dataview.setHeight(dataview.element.getHeight()); // you may or may not need this
console.log('height before\t', dataview.getHeight());
var a = new Ext.fx.Animation({
element: dataview.element,
duration: 500,
easing: 'ease-in',
preserveEndState: true,
from: {
height: dataview.element.getHeight()
},
to: {
height: 100
}
});
a.on('animationend', function (animation, element, isInterrupted) {
console.log('height before\t', dataview.getHeight());
dataview.setHeight(dataview.element.getHeight());
console.log('height set\t', dataview.getHeight());
});
Ext.Animator.run(a);
I left in some logging so you can see just what I mean. This example was written against ST 2.1 RC2.
Here's a clean utility function you can use to accomplish this
function animatron (target, prop, duration, to, from, easing) {
// return if no target or prop
if (target == null || prop == null) { return; }
// defaults
if (duration == null) { duration = 250; }
if (to == null) { to = 0; }
if (from == null) { from = target.getHeight(); }
if (easing == null) { easing = 'ease-out'; }
// to property
var t = {};
t[prop] = to;
// from property
var f = {};
f[prop] = from;
// Animation Options
var opts = {
duration: duration,
easing: easing,
element: target.element,
from: f,
preserveEndState: true,
to: t
};
// Animation Object
var anime = new Ext.fx.Animation(opts);
// On animationend Event
anime.on('animationend', function (animation, element, isInterrupted) {
// Hide the target if the to is 0
if (to == 0 && (prop == 'height' || prop == 'width')) {
if (!isInterrupted) { target.hide(); }
}
// Update property if width or height
if (prop == 'height') { target.setHeight(to); }
if (prop == 'width') { target.setWidth(to); }
// Dispatch 'animated' event to target
target.fireEvent('animated', animation, element, to, from, isInterrupted);
});
// Show the target if it's hidden and to isn't 0
if (target.getHidden() == true && to != 0) { target.show(); }
// Run the animation
Ext.Animator.run(anime);
}
You can listen for the 'animated' event on the target element
animatron(dataview, 'height', 500, 0);
dataview.addListener('animated', function (animation, element, to, from, isInterrupted) {
console.log('animation ended');
console.log('interrupted: '+ isInterrupted);
});
I have Dojo slider and I need to put from 0 to 24 h (0,1,2, ... 24).How to achive this ?
I have this:
<div id="vertical_monday" style="float: left;"></div>
and create like this :
var vertical_monday = dojo.byId("vertical_monday");
var rulesNodeMonday = document.createElement('div');
vertical_monday.appendChild(rulesNodeMonday);
var sliderRulesMonday = new dijit.form.VerticalRule({
count: 24,
style: "width:5px;"
},
rulesNodeMonday);
var slider = new dijit.form.VerticalSlider({
name: "vertical_monday",
value: 0,
minimum: 1440,
maximum: 0,
pageIncrement:100,
showButtons:true,
slideDuration:289,
discreteValues: 289,
intermediateChanges:true,
style: "height:500px;",
onChange: function(value) {
//dojo.byId("sliderValueMonday").value = value;
set_time_labels('mon',used_length['mon'],value);
val['mon']=value;
var a=(500*(value-used_length['mon']))/1440;
var temp_id='mon_'+temp_daily_plan['mon'];
$('#'+temp_id).css('height',a);
}
},
vertical_monday);
but it doesn't have numbers . How to add numbers ?
Here's a quick and dirty way to do it, not sure if it's the recommended one.
Add a container for the labels, just like you did with the ruler.
var labelsNodeMonday = document.createElement("div");
vertical_monday.appendChild(labelsNodeMonday);
Then create a dijit.form.VerticalRuleLabels instance and place it in that node, explicitly providing the labels as an array:
var labelsMonday = new dijit.form.VerticalRuleLabels({
labels: ["1h", "2h", "3h", "4h"] //.. and so on
}, labelsNodeMonday);
Or alternatively, if you don't like to repeat yourself.
var labelsMonday = new dijit.form.VerticalRuleLabels({
getLabels: function() {
for(var labels = [], i = 1; i <=24; i++) labels.push(i+"h");
return labels;
},
}, labelsNodeMonday);
Again, I don't know if this is the kosher way. Ideally, there should be something along the lines of:
var labelsMonday = new dijit.form.VerticalRuleLabels({
minimum: 1, maximum: 24, step: 1,
formatLabel: function(value) { return value + "h"; }
}, labelsNodeMonday);
.. but afaik there isn't at the moment.