Trouble Animating svgX of HighCharts Column Label - jquery-animate

BOUNTY UPDATE: I'm putting a bounty on this, so I wanted to give a little bit more information in case someone comes up with a different solution than modifying my code. The object is to animate the actual category position in HighCharts for bar and column charts. Animating the actual "bar/columns" seems to be built in to HighCharts, however, the label positions is what I'm having trouble with. See below for JSFiddle. Also, I know this question is dealing with SVG, but I need the animation support in IE8 as well.
I'm currently on a mission to animate the reorganization of categories in HighCharts for bar and column charts.
I have bar charts working fine, with the ability to reorganize categories and labels, with labels animating with the following code:
$(this).animate({'svgY': c.labelPositions[myX]}, {'duration': animDuration, 'queue': false});
Now I'm working on the columns, and I'm having significant trouble getting the labels to animate. The code is relatively the same:
$(this).animate({'svgX': c.labelPositions[myX]}, {'duration': animDuration, 'queue': false});
I'm using jQuery SVG to allow the animation of SVG elements, you can find it here.
You can view the jsFiddle I'm working on here. Just click the "Go" buttons under each chart to see them in action.
The actual "hack" to allow category animating is the Highcharts.Series.prototype.update = function(changes, callback){ function.
Just playing around trying to get something to work, I found that I could animate the svgY of the Column labels, but svgX just seems to not function at all.
Actual HighCharts.js hacks are welcome.

I took a look into your code and improved it a little bit. I did the following:
Unified code for column/bar using single variable that stores the attribute we're going to update
Removed jQuerySVG dependency, instead my code uses built-in Highcharts animate method
Fixed some minor bugs
I tested this with IE7+/Chrome/Firefox, it works fine in all of them.
Here you can find my version of Highcharts.Series.prototype.update:
Highcharts.Series.prototype.update = function (changes, callback) {
var series = this,
chart = this.chart,
options = chart.options,
axis = chart.xAxis[0],
ticks = axis.ticks,
type = chart.options.chart.type,
animDuration = 400,
attr = type === 'bar' ? 'y' : 'x',
animOpt = {},
text;
if (options.chart.animation && options.chart.animation.duration) {
animDuration = options.chart.animation.duration;
}
if (type == "bar" || type == "column") {
if (typeof chart.labelPositions === "undefined") {
chart.labelPositions = [];
$.each(ticks, function () {
chart.labelPositions.push(this.label[attr]);
});
}
for (var category in changes) {
for (var i = 0; i < series.points.length; i++) {
if (typeof series.points[i].originalCategory === "undefined") {
series.points[i].originalCategory = series.points[i].category;
}
if (series.points[i].originalCategory == category) {
$.each(ticks, function () {
text = this.label.text || this.label.element.innerHTML; // use innerHTML for oldIE
if (text == category) {
var myX = (typeof changes[category].x !== "undefined") ? changes[category].x : series.points[i].x;
series.points[i].update(changes[category], false, {
duration: animDuration
});
animOpt[attr] = parseInt(chart.labelPositions[myX]);
// This is the line that animates the bar chart labels.
this.label.animate(animOpt, {
'duration': animDuration,
'queue': false
});
return false;
}
});
}
}
}
chart.redraw();
if (typeof callback !== "undefined") {
setTimeout(function () { callback(); }, animDuration);
}
}
}
Check out the demo: http://jsfiddle.net/evq5s/17

Related

How to correctly use setInterval for multiple functions being called repeatedly in React Native

I am building a simple app in React Native that aims to flash different colors on the screen at certain time intervals. My implementation is as follows:
useEffect(() => {
var blinkOnValue;
var blinkOffValue;
function blinkOn() {
const colorAndWord = getRandomColor(colorArray);
setBackground(colorAndWord.color);
}
function blinkOff() {
setBackground('#F3F3F3');
}
if (strobeStart) {
if (on) {
blinkOnValue = setInterval(() => {
blinkOn();
setOn(false);
}, info.length * 1000);
} else {
blinkOffValue = setInterval(() => {
blinkOff();
setOn(true);
}, info.delay * 1000);
}
}
return () => {
on ? clearInterval(blinkOnValue) : clearInterval(blinkOffValue);
};
}, [colorArray, info.delay, info.length, on, strobeStart]);
The blinkOn function sets the background a certain color and the blinkOff function sets the background a default light gray-ish color. These functions should alternate back and forth, blinking on and off at different intervals. For example, if info.length is 2 and info.delay is 0.5, then the color should flash on for 2 seconds and then the screen should be light gray for 0.5 seconds and repeat. However, the duration of both of the blinkOn and blinkOff are happening for the same amount of time, no matter what the two values are. Sometimes it uses the value from info.length, and sometimes it uses the value from info.delay which is also quite strange.
I think it has something to do with components mounting and unmounting correctly but honestly I am quite lost. If anyone has any advice on how to make this code consistently work where it flashes appropriately I would really appreciate it.
Instead of trying to time your events just right, I suggest using a single timer and computing the blink state from the current system time.
var oldState = true;
function blink() {
var ms = new Date().getTime();
var t = ms % (info.delay + info.length);
var state = (t < info.length ? true : false);
if (state == oldState) return;
if (state) {
blinkOn();
}
else
{
blinkOff();
}
oldState = state;
}
Now set a short timer to check the time and update the blink state as needed:
setInterval( () => blink(), 100 );

How to making sure at least one checkbox is checked using vuejs

I would like to guarantee that at least one checkboxes are checked and the price are correct calculated.
https://jsfiddle.net/snoke/1xrzy57u/1/
methods: {
calc: function (item) {
item.isChecked = !item.isChecked
this.total = 0;
for (i = 0; i < this.items.length; i++) {
if(this.items[i].isChecked === true) {
this.total += this.items[i].price;
}
}
// fullPackagePrice
if(this.items[0].isChecked === true && this.items[1].isChecked === true && this.items[2].isChecked === true) {
this.total = this.fullPackagePrice;
}
// Trying to guarantee that have at least one checkbox checked
if(this.items[0].isChecked === false && this.items[1].isChecked === false && this.items[2].isChecked === false) {
this.total = this.items[0].price;
this.items[0].isChecked = true;
}
}
}
A good fit for this would be using computed properties instead of a method.
Read more about these here: https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties
A computed property observes all referenced data and when one piece changes, the function is re-run and re-evaluated.
What you could do is first create a allowCheckout computed property like this:
allowCheckout() {
return this.items[0].isChecked || this.items[1].isChecked || this.items[2].isChecked;
}
You will then use it within the button like this:
<button :disabled="allowCheckout"...
This will disable the button when no items are checked.
Next, you'll also want to create a second computed property for the total price
totalPrice() {
// Perform similar checking here to update this.total
}
Lastly, you'll want to change your checkboxes to no longer use v-on:change but to instead use v-model for the relevant parameter for each.
This way your checkbox status will be bound to the true/falseness of the variables.
If you still want to go with your method, you can implement at like shown in this updated fiddle and set a variable atLeastOneItemIsChecked like this:
this.atLeastOneItemIsChecked = this.items.find(item => item.isChecked) !== undefined
Do not force the user to always check a checkbox. Instead, display a hint and disable the button using :disable and tailwind css resulting in this:

Can I detect changes in a node's markup text using dojo?

I have a bunch of nodes that will contain markup in an unpredictable structure. I want to be able to watch these nodes and see if the html of the any of the child nodes or their descendants change, no matter how slightly. If they do, then I want to fire an event.
Can I do this through dojo? I'm using 1.10, the latest one.
Thanks.
It sounds like you're looking for dom mutations. As far as I'm aware dojo does not provide an api for this, but they're pretty simple to set up. The problem is different browsers have different ways of doing this.
var observeNode = document.getElementById('observeMe');
// Check for vendor-specific versions of MutationObserver.
MutationObserver = (function() {
var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
for (var i=0, il=prefixes.length; i<il; i++) {
if (prefixes[i] + 'MutationObserver' in window) {
return window[prefixes[i] + 'MutationObserver'];
}
}
}());
// Sniff for MutationObserver support
if (MutationObserver) {
var observer = new MutationObserver(function(mutations) {
alert('Something changed!');
});
observer.observe(observeNode, {attributes: true, childList: true, characterData: true});
} else {
// Fall back to mutation events
if (observeNode.addEventListener) {
observeNode.addEventListener('DOMSubtreeModified', function() {
alert('Something changed!');
});
}
// IE8 and below has its own little weird thing
else {
observeNode.onpropertychange = function() {
alert('Something Changed!');
}
}
}

Transition with keepScrollPosition and navigateBack

We are using Durandal for our SPA application and came to a, in my opinion, common use case. We have two pages: one page is a list of entities (with filters, sorting, virtual scroll) and another is detail preview of an entity. So, user is on list page and set a filter and a list of results comes out. After scrolling a little bit down user notice an entity which he/she would like to see details for. So clicking on a proper link user is navigated to details preview page.
After "work finished" on preview page user click back button (in app itself or browser) and he/she is back on the list page. However, default 'entrance' transition scroll the page to the top and not to the position on list where user pressed preview. So in order to 'read' list further user have to scroll down where he/she was before pressing preview.
So I started to create new transition which will for certain pages (like list-search pages) keep the scroll position and for other pages (like preview or edit pages) scroll to top on transition complete. And this was easy to do however, I was surprised when I noticed that there are strange behavior on preview pages when I hit navigateBack 'button'. My already long story short, after investigation I found out that windows.history.back is completing earlier then the transition is made and this cause that preview pages are scrolled automatically down to position of previous (list) page when back button is hit. This scrolling have a very unpleasant effect on UI not mentioning that it is 'total catastrophe' for my transition.
Any idea or suggestion what could I do in this case?
Here is the code of transition. It is just a working copy not finished yet as far as I have this problem.
define(['../system'], function (system) {
var fadeOutDuration = 100;
var scrollPositions = new Array();
var getScrollObjectFor = function (node) {
var elemObjs = scrollPositions.filter(function (ele) {
return ele.element === node;
});
if (elemObjs.length > 0)
return elemObjs[0];
else
return null;
};
var addScrollPositionFor = function (node) {
var elemObj = getScrollObjectFor(node);
if (elemObj) {
elemObj.scrollPosition = $(document).scrollTop();
}
else {
scrollPositions.push({element: node, scrollPosition: $(document).scrollTop()});
}
};
var scrollTransition = function (parent, newChild, settings) {
return system.defer(function (dfd) {
function endTransition() {
dfd.resolve();
}
function scrollIfNeeded() {
var elemObj = getScrollObjectFor(newChild);
if (elemObj)
{
$(document).scrollTop(elemObj.scrollPosition);
}
else {
$(document).scrollTop(0);
}
}
if (!newChild) {
if (settings.activeView) {
addScrollPositionFor(settings.activeView);
$(settings.activeView).fadeOut(fadeOutDuration, function () {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
});
} else {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
}
} else {
var $previousView = $(settings.activeView);
var duration = settings.duration || 500;
var fadeOnly = !!settings.fadeOnly;
function startTransition() {
if (settings.cacheViews) {
if (settings.composingNewView) {
ko.virtualElements.prepend(parent, newChild);
}
} else {
ko.virtualElements.emptyNode(parent);
ko.virtualElements.prepend(parent, newChild);
}
var startValues = {
marginLeft: fadeOnly ? '0' : '20px',
marginRight: fadeOnly ? '0' : '-20px',
opacity: 0,
display: 'block'
};
var endValues = {
marginRight: 0,
marginLeft: 0,
opacity: 1
};
$(newChild).css(startValues);
var animateOptions = {
duration: duration,
easing : 'swing',
complete: endTransition,
done: scrollIfNeeded
};
$(newChild).animate(endValues, animateOptions);
}
if ($previousView.length) {
addScrollPositionFor(settings.activeView);
$previousView.fadeOut(fadeOutDuration, startTransition);
} else {
startTransition();
}
}
}).promise();
};
return scrollTransition;
});
A simpler approach could be to store the scroll position when the module deactivates and restore the scroll on viewAttached.
You could store the positions in some global app variable:
app.scrollPositions = app.scrollPositions || {};
app.scrollPositions[system.getModuleId(this)] = theCurrentScrollPosition;

Is it possible to filter data in a dgrid like you can in a datagrid? If so, how?

I'm relatively new to dojo and have seen how datagrid offers a dynamic filtering capability that reduces the visible rows based on what you type into a filter text input. I have not found any examples of how to do it with the dgrid. If it can be done, please provide an example or point me to a resource that offers a tutorial or example. Thanks!
Yes, it is possible. Use dgrid/OnDemandGrid and define query function that will return true or false based on your logic for each row in dojo/store powering the grid.
I prepared an example to play with at jsFiddle: http://jsfiddle.net/phusick/7gnFd/, so I do not have to explain too much:
The Query Function:
var filterQuery = function(item, index, items) {
var filterString = filter ? filter.get("value") + "" : "";
// early exists
if (filterString.length < 2) return true;
if (!item.Name) return false;
// compare
var name = (item.Name + "").toLowerCase();
if (~name.indexOf(filterString.toLowerCase())) { return true;}
return false;
};
The Grid:
var grid = new Grid({
store: store,
query: filterQuery, // <== the query function for filtering
columns: {
Name: "Name",
Year: "Year",
Artist: "Artist",
Album: "Album",
Genre: "Genre"
}
}, "grid");
I know this isn't the answer to the question asked, and the answer provided is masterful and we use it quite a bit.
However, this functionality doesn't seem to work well at all if you're using a TreeGrid (columns with the "dgrid/tree" plugin). I've written some code to simulate the same behavior as the accepted answer with a tree grid. It's basically just looping through the items in the store and hiding any row elements that don't match whatever condition you set forth. Thought I would share it in case it helps anybody. It's rather ugly and I'm sure it can be improved upon, but it works.
It basically uses the same concept as phusick's answer. You need to watch a value on a TextBox, but instead of refreshing the grid you have it call a function:
textBox.watch("value", lang.hitch(this, function() {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
};
timeoutId = setTimeout(lang.hitch(this, function() {
this.filterGridByName(textBox.get('value'), myGrid);
}, 300));
}));
And here's the function:
filterGridByName: function(name, grid){
try {
for (var j in grid.store.data){
var dataItem = grid.store.data[j];
var childrenLength = dataItem.children.length;
var childrenHiddenCount = 0;
var parentRow = grid.row(dataItem.id);
for (var k in dataItem.children){
var row = grid.row(dataItem.children[k].id);
var found = false;
if (dataItem.children[k].name.toLowerCase().indexOf(name.toLowerCase()) != -1){
found = true;
}
if (found){
if (row.element){
domStyle.set(row.element, "display", "block");
}
if (parentRow.element){
domStyle.set(parentRow.element, "display", "block");
}
} else {
childrenHiddenCount++;
// programmatically uncheck any hidden item so hidden items
for (var m in grid.dirty){
if (m === dataItem.children[k].id && grid.dirty[m].selected){
grid.dirty[m].selected = false;
}
}
if (row.element){
domStyle.set(row.element, "display", "none");
}
}
}
// if all of the children were hidden, hide the parent too
if (childrenLength === childrenHiddenCount){
domStyle.set(parentRow.element, "display", "none");
}
}
} catch (err){
console.info("error: ", err);
}
}