0

I'm trying to add class x and then class y to an element in an angular service/directive (it must be in a script and not via ng-class). the sequence is critical - I want x to be added first and only then y - it needs to be 2 steps.

I was sure this would work:

$(element).addClass('x');
$timeout(function () {
  $(element).addClass('y');
},0)

But it turns out that sometimes it is not consistant and that y is being added before x. I thought $timeout will garauntee a 2 step process - but I'm asking if there is another way to garauntee it. maybe an 'onload' or 'onupdateClass' event on the DOM element that I don't know if it exists?

Clarification: It is not important if eventually it will be 'x y' or 'y x'. The string order is not important. what is important is the sequence: first add x, wait for it to be added and then add y

Alon
  • 7,618
  • 18
  • 61
  • 99
  • `$timeout` is not making any sense here...There must be something else which is causing issue... – Rayon Feb 26 '16 at 10:22
  • 2
    And what's wrong with `$(element).addClass('x y');`??? Anyway, looks like a XY problem. And maybe just your `y` class is added before you are calling your snippet, by something else. Still not sure why the order is critical?! To get specifc ordr at time snippet was called, you could use: `$(element).removeClass('x y').addClass('x y');` – A. Wolff Feb 26 '16 at 10:23
  • 1
    `$(element).addClass('x').addClass('y');` if you don't want to use `$(element).addClass('x y');` – MartijnK Feb 26 '16 at 10:23
  • I'm trying to animate something from {display none; opacity: 0} to {display:block; opacity:1}. it requires 2 steps: 1. add display:block; opacity 0; 2 add opacity: 1. if I add it in one step the animation will not work – Alon Feb 26 '16 at 10:25
  • @MartijnK But this has exact same behaviour as just passing two classes to `addClass()` method – A. Wolff Feb 26 '16 at 10:25
  • You should be using CSS animation for that, then you can do it with a single class, as it will handle your transitions for you. – MartijnK Feb 26 '16 at 10:27
  • @A.Wolff or he can add the second class on the added first class like `$('#button').addClass('first'); $('.first').addClass('second');` would this work? – guradio Feb 26 '16 at 10:27
  • @guradio But i suspect the `y` class is added by some other code before the `x` one, and so readding `y` class has no effect – A. Wolff Feb 26 '16 at 10:29
  • @A.Wolff I'm pretty sure the problem is trusting timeout to make a sequence and that it is not reliable enough. – Alon Feb 26 '16 at 10:53
  • @Alon `addClass()` is synchron so no the probelm cannot be the timeout, which is async and put callback in event queue (delayed). You still didn't explain why would you need specific 'order' and what is your issue (CSS animation/transition/or what?). We are only talking about the workaround you think would fix it. Please read [the XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) (very well named in your case ;) ) – A. Wolff Feb 26 '16 at 11:04

3 Answers3

2

Try like this:

$(element).addClass('x');

if ($( element).hasClass('x')) {
     $(element).addClass('y');
}
Sumanta736
  • 695
  • 3
  • 10
  • Didn't think of that! might be the solution – Alon Feb 26 '16 at 10:30
  • This code has no meaning regarding provided context, imho – A. Wolff Feb 26 '16 at 10:31
  • @A.Wolff I'm basically writing a directive/service that MUST put the classes in the right order. this is basically the main thing it does. I just don't understand how do I know for sure that I manipulate the DOM in 2 steps. I was sure timeout will do the trick, but it's probaly not the bullet-proof I was aiming for – Alon Feb 26 '16 at 10:33
  • @Alon `that MUST put the classes in the right order` Why that??? And again, if the `y` class is already added, adding it again would have no effect. Menaing if your element is `class="y"` then adding `x` would give `class="y x"` then adding `y` would still result in `class="y x"`. This answer would not fix it in any way – A. Wolff Feb 26 '16 at 10:36
  • @A.Wolff I'm sorry I was probably not clear enough. the order of the string is NOT important. The timing is important. first x, then y. That's why the above answer might work because it will not add y before x was already added (and then I can set another timeout to check if this rule is working and then add y) – Alon Feb 26 '16 at 10:47
  • Exactly its not for the order, it checks if one class is already added or not, then add another class. – Sumanta736 Feb 26 '16 at 10:53
  • 1
    @Alon then adding the class y to class x would also be feasible – guradio Feb 26 '16 at 10:57
  • 1
    @guradio Ya, it just looks like i misunderstood expected behaviour (issue) – A. Wolff Feb 26 '16 at 11:06
1

If you want to add a delay between adding the classes, you could use queue() as show in this example: https://stackoverflow.com/a/2510255/390421

Community
  • 1
  • 1
MartijnK
  • 642
  • 5
  • 19
  • The thing is if `y` class is already set before `x`, then adding it again even using any delay wouldn't have any effect, meaning the order would still be `y x` – A. Wolff Feb 26 '16 at 10:28
  • I'm trying to do it without jQuery, only with jqLite that comes with angular. though I need to check if queue works cause that might help. I was sure queue only manages setTimeout but it probably does something else – Alon Feb 26 '16 at 10:30
1

The following function's idea is based on the idea shown in the question, but it handles a repeated setTimeout with a limited timeouts. This function adds the class and processes to the next if all the perequisite classes were added and schedules itself to run a little bit later if the perequisites were not added yet. However, since Javascript is not multithreaded, I believe that the real problem was that y was added somehow to element before you added x. A console.log(element.hasClass(y)) might help you out.

function addClasses(element, classes, time, index, timeouts) {
    if (index === classes.length) {
        //All classes were added
        return;
    } else if ((index === 0) || (element.hasClass(classes[index - 1]))) {
        //Add the element and process to the next one
        element.addClass(classes[index++]);
        addClasses(element, classes, time, index, timeouts);
    } else {
        //If there are no more shots, then handle the error
        if (--timeouts === 0) {
            //handle error
            return;
        }
        //Schedule the function to run again
        setTimeout(function() {
            addClasses(element, classes, time, index, timeouts);
        }, time);
    }
}

And then you can use the function like this:

addClasses($(element), ["x", "y"], 10, 0, 10);
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175