The right way to access an element programmatically at a given time


#1

Hi guys :smile:

Just a quick noob question :stuck_out_tongue:

I have a function with a for loop that adds a reference to an array to mark that place as reserved, the for loop below:

for (var loopControl = 0, loopLength = shipLength(shipType); loopControl < loopLength; loopControl++) {
		addBlock = $scope.selectedX.toString() + ($scope.selectedY - loopControl).toString();
		$scope.takenBlocks.push(addBlock);					
	}

This is working perfectly and all is well, but while I am adding the reference to the loop I also want to mark the place as reserved by placing an X over it or adding a CSS class, maybe even an image, many possibly options I suppose, but I am unsure of the RIGHT way to do this.

The addBlock value is also the ID of the element, so hacking the DOM would WORK, but wouldn’t be RIGHT now would it? :wink:

Do any of you Angular magicians have some advice for me please? I thought about using a custom directive but I am not sure how apply it programmatically at the right time.

To give you an idea of the outcome, if I were using full JQuery it would look something like this:

for (var loopControl = 0, loopLength = shipLength(shipType); loopControl < loopLength; loopControl++) {
		addBlock = $scope.selectedX.toString() + ($scope.selectedY - loopControl).toString();
		$scope.takenBlocks.push(addBlock);					
                $('#'+addBlock).css.text = 'X';
	}

Thanks guys much appreciated :slight_smile:


#2

There are a few ways, but one standard way is to use ng-class to render an alternate (or additional) css class, depending on a value in your object.

It’s not immediately clear how your HTML is rendered from this snippet, but if you imagine you have an array of objects, $scope.myBlocks, each one of which has an isSelected property set to “true” where that object has been selected, and a description property for the, er, description.

You’d typically render in a loop like this:

<div ng-repeat="block in myBlocks" class="blockFormat" ng-bind="block.description"></div>

generating one div per object in myBlocks

If you want the selected ones to display differently, add ng-class

<div ng-repeat="block in myBlocks" class="blockFormat" ng-class="{'selectedBlock': block.isSelected}" ng-bind="block.description"></div>

so any object with isSelected === true will be rendered with class="blockFormat selectedBlock"


#3

Hi @mrsean2k thanks a lot for the response.

I am indeed using an ng-repeat to create divs.

The issue though is that I don’t want to apply the class or the change to the object on click or when it’s active… I First need to check a few things and if the checks pass, programmatically add the class or change the element.

Let me know if you would need some additional code to help it make more sense :slight_smile:

Thanks for the help!


#4

The angular docs are actually pretty decent explaining this now, but briefly I’ll return to your subject: “The right way to access an element programatically at a given time”

The very broad answer is that you don’t think in terms of accessing elements.

In jQuery it’s idiomatic to store at least some state about your object - is it enabled? can it be clicked? what colour should it be? - in the element itself with classes, and to check the state of an element by looking at the classes currently set.

Angular positively discourages this; you create a model with all of the state you might want to represent - selected, active, highlightColour, whatever - and you generate html from that model. All your logic looks at the model, it doesn’t look at the html that’s being generated for clues.

In the example here, you want to:

  1. indicate if it’s possible to click on an element and

  2. do something if a qualifying element is clicked

Taking (1) first, you might want to add a class that includes changing the cursor / colour if an item is selectable. The ng-class value can use functions instead of values, so you could write:

<div ng-repeat="block in myBlocks" class="blockFormat" ng-class="{'clickableBlock': canClick(block)}" ng-bind="block.description"></div>

on your scope, write a canClick function. This will be passed the block as a parameter and you can return true or false depending on wether or not the value can be clicked.

$cope.canClick = function(testBlock) {
  if (testBlock.... blah blah blah) {
    return true;
  } else {
    return false;
  }
};

For (2) you want to use the ng-click event to check whether or not a value should respond to a click and toggle something else on or off. Add it to the repeated element and add a function to your scope like before:

<div ng-repeat="block in myBlocks" ng-click="toggleSelection(block)" class="blockFormat" ng-class="{'clickableBlock': canClick(block)}" ng-bind="block.description"></div>

$scope.toggleSelection = function(testBlock) {
  if ($scope.canClick(testBlock)) {
    testBlock.isSelected = !testBlock.isSelected;
  }
}

so you re-use the function to work out if an element is clickable, and toggle a bit of it’s state depending.

When a block has actually been selected, you also probably want to indicate it’s selected state. ng-class can take multiple test values and set multiple classes, so to add another class on the basis of the isSelected property, use this:

<div ng-repeat="block in myBlocks" ng-click="toggleSelection(block)" class="blockFormat" ng-class="{'clickableBlock': canClick(block), 'selectedBlock': block.isSelected}" ng-bind="block.description"></div>

There are lots of variations, but this is the principle; you always manipulate the data behind the model; you can use functions bound to ng-click to change bits of it; the html is then regenerated from the new model.


#5

Thanks very much to @mrsean2k for the help!

For those who may come across this at a later date, this is absolutely correct.

Add a property to an object on the scope which you can toggle and then use ng-class to execute the UI changes as the value toggles.

Thanks again :smile:


#6

Sorry v. tied up with a release - thanks for the thanks, and you’re welcome!

Angular’s a bit odd in some places, and the official docs used to have a strange emphasis on giving examples with very clever uses that made it hard to see how to use something in a “normal” way, but it’s getting much better.

Cheers

S