Swapping elements in Angular JS - javascript

I was curious what the appropriate way is to animate and swap elements using angular. I have a two-dimensional array that has a nested ng-repeat to create the table. My end goal is to have two elements in the table swap places using an animation, so basically just hover over to eachother's positions.
I have this function that swaps the position of two jquery elements, but it doesn't update the array and ultimately the angular array will no longer be synced with what is displayed. I also find that the animation hangs a little when moving elements between rows whereas columns are smooth. ie. [0][0] -> [0][1] is smooth and [0][0] -> [1][0] hangs
jQuery.fn.swapWith = function (to, callback) {
animating = true;
thisPos = this.position();
toPos = to.position();
$.when(this.animate({
top: toPos.top,
left: toPos.left
}, 300),
to.animate({
top: thisPos.top,
left: thisPos.left
}, 300)).done(function () {
animating = false;
if (callback) {
callback();
}
});
};
I've also found this codepen which is pretty nice, but also not quite what I'm looking for. http://codepen.io/daleyjem/pen/xbZYpY Is there a standard way of handling something like this in angular?
To put in more simple terms: If I want array[0][0] to swap positions with array[0][1] and have the $scope object update appropriately, how should I do it? Same with array[0][0] to array[1][0]?
EDIT: I should mention, I have noticed that simply setting the elements equal to eachother will update the table appropriately. The problem being the lack of animation. example:
a = [0][1]
b = [0][0]
[0][1] = b
[0][0] = a

Related

Parse shape movement from createjs tween?

I'm trying to pre-calculate the bounding boxes of objects in a createjs file.
I have a simple recursive loop that loops over the tween.
I'm having trouble understanding how the data is stored.
My understanding is so far:
tween._stephead - the start of the tween
tween._stephead.next - the next tween target, with multiple .next objects
and mixed in between there seem to be some ease objects.
For each of the tween targets, I read the props, and move the bounding box of my shape according to the values x and y.
This seems to work okay for single objects on a tween, but as soon as there are multiple objects in a tween, this falls apart, due to the fact that its stored in an array, and objects not in the array are still visible on screen.
Ive looked over the API and haven't found any other way to do pre-calculation of objects movements... Am I going about this right?
I ended up using a simpler method, just play the demo though without displaying it to the user.
for (var f=0; f<totalFrames; f++) {
stage.update();
var c = exportRoot.numChildren;
while(c--) {
var child = exportRoot.children[c];
...
}
}

Best way to present editable tables

I've inherited the job of maintaining and developing an internal journaling system for registering inventory in tables on a local website. It is a website made in PHP, using jquery and handontable to list data from a MySQL database. All fields in the table are editable by the users.
Today the loading of data can be slow (10-15 seconds in the largest tables), which is mainly because of the loops used to populate the table and adjust the column sizes.
What do you think would be the best way to fix this issue? Should I reduce load times by fixing the loops, and keep handsontable as table library? Or should I scrap the old solution and implement something new?
Thanks :)
EDIT
I just saw you're using handsontable so my answer doesn't really provide a solution, as handsontable already uses a kind of list virtualization. I'll leave my answer anyway
Original Answer
What you can probably do is some sort of list virtualization, although this might be a bit tricky with table elements because you need absolute positioning and control of heights. Also it generally assumes that all rows have the same height.
The general idea is you only want to bother with rendering what's currently on the screen. Assuming you can fit 50 rows into your viewport at any time, you're measuring and updating 650 rows that don't matter. If you have 500000 rows, like in the fiddle, you're problem is going to be exponentially out of control.
Without knowing what you're doing exactly, here's a very general approach to the problem:
var elements = [];
var maxLength = 500000; // Number of elements we're going to generate
var itemHeight = 20; // We need a static row height for this to work
var totalHeight = itemHeight * maxLength; // The total height of the content
var $scrollContainer = $('#scroller-container'); // The container that will scroll
var $scrollContent = $('#scroller-contents'); // The content container for our items.
// We need to set the total height of the content so that absolute positioning works and the container receives the correctly sized scroll bar.
$scrollContent.css({ height: `${totalHeight}px` });
// Generate elements.
for (let i = 0; i < maxLength; i++) {
elements.push({
name: `item_${i}`,
value: `value_${i + 100}`
});
}
// By taking some measurements we will find out
// here exactly what items need to be rendered.
function obtainRenderableItems () {
// The size of our scrollable container
var containerHeight = $scrollContainer.height();
// How many items will fit inside the viewable area of our scrollable container
var viewport_count = Math.ceil(containerHeight / itemHeight);
// Where is it currently scrolled to.
var scrollPosition = $scrollContainer.scrollTop();
// The index of the first item in the viewable area
var start = Math.floor(scrollPosition / itemHeight);
// This calculation gives us a number of items to buffer on either side
// which prevents some janky behaviour when scrolling over yet unrendered items
var preScan = start - viewport_count <= 0 ? 0 : start - viewport_count;
// Basically we get the elements visible on the viewports by the current start
// index, and a buffer at the beginning and the end of the same amount of items
// in the viewport.
return elements.slice(preScan, preScan + (viewport_count * 3)).map((element, index) => {
return [preScan + index, element];
});
};
// Convert it to HTML, you can do whatever here, demo only.
function generateHTML (elements) {
return elements.map(el => {
let div = document.createElement('div');
div.className = 'element';
div.style.height = `${itemHeight}px`;
div.style.top = `${el[0] * itemHeight}px`;
div.innerHTML = `${el[1].name} - ${el[1].value}`;
return div.outerHTML;
}).join('');
}
// When we scroll we recalculate what items need to be shown and rerender them
// inside the page.
function onScroll (event) {
let items = obtainRenderableItems();
let htmlContent = generateHTML(items);
$scrollContent.html(htmlContent);
}
$scrollContainer.scroll(onScroll);
// Run at the beginning
onScroll();
The jQuery example above is based on a React component I wrote for exactly this purpose. You'll have to excuse my jQuery I haven't used it in years.
See the fiddle
There are a number of caveats with this approach. The major one being the row height must be the same for all rows, which is not workable for a number of situations. It also relies on a fixed container height, although the flex model can work around this.

JS, DOM. Delete several elements from DOM using user intput value

Example on Codepen below. Can anybody explain as simple as posible how to delete a number of elements from DOM based on a user input value (just an integer). I have a path(SVG curve). User specifies a number of steps(dots) to reach the end point of the path. Then ship will travel to the end point, after some event ( for test I'm using click event ). User will specify a number of steps per move for each event ( event done ship start moving ). And by moving i want to delete dots on the pass that are left behind.
var trigger = window.addEventListener('click', function(){
var steps = parseInt(prompt("Select Number Of Steps Per Move "),10);
var positionVal = parseFloat(window.getComputedStyle(el,null).getPropertyValue("motion-offset"));
el.animate([
{ motionOffset: positionVal + "%" },
{ motionOffset: positionVal + 100/(userValue - 1)*steps + "%" }
],
{duration: 5000,
direction: 'alternate',
fill: 'forwards',
iterations: 1,
easing: 'ease-in-out'
});
function deleteThis () {
dotsArray.splice(positionVal, steps)
var dots = document.getElementsByClassName("dots");
for (i=0;i<steps;i++) {
dots[i].remove();
}
}
window.setTimeout(deleteThis,3000);
});
Codepen Example
If you want to remove DOM elements:
for (i=0;i<steps;i++) {
dotsArray[0].remove();
dotsArray.splice(0,1);
}
Sounds like you're asking how to remove elements from the DOM, which happen to be stored in an Array.
The Array that happens to be holding some DOM elements and the DOM/elements themselves are completely unrelated concepts. There will be no native Array method for dealing with the DOM, because Arrays are not a DOM concept, they are a general programming construct of JS. They could be holding DOM elements, Strings, Numbers, other Arrays, Objects, null Objects, or a combination of all the above...or more. There is nothing about Arrays that is specific to the DOM...that just happens to be what you have shoved into that Array.
For that you're simply gonna need to loop through the Array and remove the elements. I'd recommend making a function to do so if you're gonna be doing it a lot:
// removes the elements in the Array from the DOM, then removes them from the Array
function removeArrElemsFromDOM(arr, start, howMany)
{
for (var i=start; i<start+howMany; i++)
arr[i].parentNode.removeChild(arr[i]);
arr.splice(start, howMany);
}
You'd use like removeArrElemsFromDOM(myArray, 0, 2); would remove the first 2 elements of the array from the DOM (and also then cut them from the Array as well).
JSFiddle showing it working

Infinite loop when using generic solution to split up carousel contents

Basically, I'm getting an infinite loop and maybe I'm working too hard but I can't see why.
Context:
I'm using a carousel (Bootstrap's). The contents of the carousel is generated and pushed into one carousel slide, then the goal is to take the contents and split it up into multiple slides if the number of items inside surpass a certain pre-defined max-length property (5). I got this working fine for a specific use case of the carousel (a table being spread across the multiple slides if there are more than 5 table rows), but it's not generic enough. What happened is that the JS would take the overflown table rows (i.e. of index 5 and up), create a new slide from a harcoded HTML string in the function (a slide div containing all the markup for the table yet empty) and push those extra rows into it.
To make it more generic, I've decided to use classes like carousel_common_list and carousel_common_item which would be applied to the tbody and trs in the case I've explained. Then, I've to handle the template in a decoupled way. What I've tried to do is, take a clone of the original sole slide, empty the carousel_common_list and push any overflown carousel_common_items into it, and so on. But I get an infinite loop.
Code
What I've called a slide so far is called an item in the code (to match Bootstrap's carousel's item class for slides).
var carousels = $('div.carousel'),
carouselCommonListClass = 'carousel_common_list',
carouselCommonItemClass = 'carousel_common_item',
items_per_slide = 5;
$.each(carousels, function (index, element) {//for each carousel
var $carousel = carousels.eq(index),
$items = $carousel.find('.item');
var getItemTemplate = function ($item) {
$copy = $item.clone();//take the html, create a new element from it (not added to DOM)
$copy.find('.' + carouselCommonListClass).empty();
$copy.removeClass('active');
return $copy;
}
var splitUpItem = function ($item, $itemTemplate) {
var $bigList = $item.find('.' + carouselCommonListClass), group;
while ((group = $bigList.find('.' + carouselCommonItemClass + ':gt(' + (items_per_slide - 1 ) + ')').remove()).length) {
var $newItem = $itemTemplate;
$newItem.find('.' + carouselCommonListClass).prepend(group);
$newItem.insertAfter($item);
splitUpItem($newItem, $itemTemplate);//infintely called
}
}
//foreach item
$.each($items, function (item_index, item_element) {//for each slide, in each carousel
var $item = $items.eq(item_index);
splitUpItem($item, getItemTemplate($item));
});
});
FYI, this works like expected when the line marked with //infintely called is commented out; i.e. splits one oversized slide into one slide of items_per_slide length and another slide (which could be over items_per_slide in length if the original sole slide was over items_per_slide * 2 in length.
Also, I took this answer and modified it for the contents of splitUpItem().
Note:
I know it's not the most usable or accessible solution to split tables, lists, etc. over multiple slides like I am, but if you've a better idea answer my open question on that.
You're not getting an infinite loop per se, in that you're not infinitely stuck in the same while loop. As you mention, when you remove the //infinitely called line you're fine. The first pass through that while loop, the length you compute will equal the number of items (with gt:(4)) in all the lists in $item. You then remove all those items, so the next pass through will have that number equal to 0. This will always be the behaviour of that loop, so it really doesn't need to be a loop, but that's not the main problem.
The problem is that it's a recursive call. And the only guard you have against making the recursive call infinite is the condition in your while loop, but that condition will always be met the first pass through. In fact, if $item has 5 lists, each with 3 items with gt:(4), then $newItem will have 5 lists, each with 5 x 3 = 15 items. So when splitUpItem gets called on $newItem, the condition in your while loop will again first be non-zero. And then it'll get called again, and that number will be 5 x 15 = 75. And so on. In other words, you're recursively calling this function, and your guard against this call being made infinitely many times is to check that some number is 0, but the number there will actually grow exponentially with each recursive call of splitUpItem.
Hope that answers your question about why it's "infinitely looping." Gotta get to work, but I'll try to suggest a better way to split up the slides tomorrow if no one else has by then.

Is it possible to programmatically define a function from a string or some other technique without using eval()?

I am working on coding for a situation where I need to construct a function of nested callbacks of an unknown length. It is to create a sequenced animation queue to move an element across an unknown # of positions.
For example, output would look something like this with X 'complete' callbacks nested inside:
$('#element').animate(css, { complete: function () {
$('#element').animate(css, { complete: function () {
// more nested calls inside
}
});
Right now I am generating these functions as a string, and then once completed, feeding it to new Function():
myFunc = new Function(generatedFuncString);
The content is trusted but this still uses eval() which has negative performance implications. I was just wondering if there is another/better way?
edit: The reason I am doing it this way is because I have a very complicated set of animations to perform and am working outside of the jQuery animation queue. If anyone has a better suggestion for how to accomplish a situation like this that would be helpful...
Imagine a baseball diamond with a runner(A) on 1st and a runner(B) on 3rd. In one animation bundle, I want to animate runner A to 3rd (stopping at 2nd in the middle, 2 advances), and runner B to HOME (1 advance).
I have to fire-off the initial advance with 'queue: false' so that runner A and B move to their first base at the same time (runner A to 2nd, runner B to home).
When Runner A is done moving to 2nd, I want to then move him to 3rd (hence constructing a animate() call with nested callbacks pro grammatically to ensure this sequencing is preserved).
The reason I am constructing the function via string is because I know what the inner-most callback is going to be first, and then recursively constructed 1 or more outer-callbacks from there. I couldn't figure out a way to do this by working with functions as objects and keeping all of the references in tact.
Keep in mind this is a simple example. Imagine a situation where the bases are loaded, and I need to animate a grand slam (all 4 runners circle all bases, runner originating at home needs to make 3 stops before running back to home). Etc etc.
Answering the question you ask in your title: You can create functions from strings via eval, new Function, and by inserting a script element with the text you want. But it all comes to the same thing: Firing up the JavaScript parser and creating the function.
But rather than nesting, I think you want chaining. Build a list of the animations in an array, and use the animate callback to call the next animation in the array. Something like:
var animations = [
/* css for first animation */,
/* css for second animation */,
/* etc. */
];
var index = 0;
function runAnimation() {
if (index < animations.length) {
$("#element").animate(animations[index++], runAnimation);
}
}
You'd build up the array dynamically, of course.
gdoron points out in the comments that if all of your animations are on the same element, it can be even simpler:
var animations = [
/* css for first animation */,
/* css for second animation */,
/* etc. */
];
var index = 0;
for (index = 0; index < animations.length; ++index) {
$("#element").animate(animations[index]);
}
...because when you call animate multiple times on the same element, by default the animations queue up (the queue option defaults to true; sadly the docs don't seem to say that). My code example above doesn't rely on the queue, and so in theory each entry in the array could be an object with a property for the selector for the elements to animate and the css to apply. But if it's all one element, you can just use a straight loop.

Categories