0

I have an angular function that I assigned to the click event of a button using the ng-click directive. I pass to it the $event object so I could retrieve the button that was clicked. After retrieving the button using $event.currentTarget, I would like to get a parent element using .closest('.parentDiv'), and search inside that element using .find('.item-id').

I have that following code but it gives me an error saying .find() is not a function:

$scope.addToCart = function ($event) {
    var itemId;
    var qty;
    var btn = $event.currentTarget;
    itemId = btn.closest('.parentDiv').find('.item-id').val();        
};

Thanks in advance.

RonyLoud
  • 2,408
  • 2
  • 20
  • 25
Rian
  • 171
  • 16
  • 3
    When you find yourself doing stuff like this in angular, you know you're doing something wrong. You should probably have an array of objects that represent items, displaying them in an ng-repeat. When you click the button that calls addToCart, you should then pass in the entire item into the function, and you have access to all of its properties (i.e. price, id, etc.). I can put together a quick demo for you if you'd like? – mhodges Dec 05 '16 at 16:22
  • Can you try if this works? `$(btn).closest('.parentDiv').find('.item-id').val()` – Gerard Reches Dec 05 '16 at 16:25
  • @GerardReches I think that would work but I need to use a function within my controller coz I need to update some scope variables afterwards. – Rian Dec 05 '16 at 16:29
  • @mhodges Yes, you're right. It's my first time trying angular and I also feel that I am not doing it right. I'll try to change my approach to this and try to do something like what you suggested. Thanks. – Rian Dec 05 '16 at 16:31
  • 2
    `$event.currentTarget` returns a DOM element if I'm not wrong. You can't use jQuery functions in a DOM element, so you should cast it to a jQuery object. `btn` is a DOM element, and `$(btn)` is a jQuery object of that element. – Gerard Reches Dec 05 '16 at 16:33
  • @GerardReches That worked! Although just like what mhodges suggested, maybe I should rethink my approach. I would mark your solution as the answer if you post it as an answer. Thanks. – Rian Dec 05 '16 at 16:45
  • 1
    You should read the answer to [“Thinking in AngularJS” if I have a jQuery background?](http://stackoverflow.com/a/15012542/542251) Tl;Dr pretty much never mix jQuery and angular. – Liam Dec 05 '16 at 16:46

3 Answers3

2

The more true Angular approach to this problem would be to do as I suggested in my comment, which is to have an array of objects that represent items, displaying them in an ng-repeat. When you click the button that calls addToCart, you should then pass in the entire item into the function, and you have access to all of its properties (i.e. price, id, etc.).

Here's what the bare-bones code would look like:

var app = angular.module("myApp", [])
  .controller("myCtrl", function($scope) {
    $scope.shoppingCart = [];
    $scope.items = [{
      id: 1,
      price: 1.49,
      quantity: 0,
      name: "Soup"
    }, {
      id: 2,
      price: 4.75,
      quantity: 0,
      name: "Chicken"
    }, {
      id: 3,
      price: 2.29,
      quantity: 0,
      name: "Beef Jerky"
    }, {
      id: 4,
      price: 3.00,
      quantity: 0,
      name: "Salad"
    }, {
      id: 5,
      price: 0.99,
      quantity: 0,
      name: "Avocado"
    }];
    $scope.getCartTotal = function() {
      return $scope.shoppingCart.reduce(function(sum, curr) {
        return sum + (Number(curr.quantity) * curr.price);
      }, 0);
    };
    $scope.removeFromCart = function(cartItem) {
      var item = $scope.items[$scope.items.indexOf(cartItem)];
      item.quantity = 0;
      $scope.shoppingCart.splice($scope.shoppingCart.indexOf(cartItem), 1);
    };
    $scope.addToCart = function(item, itemForm) {
      var newQuantity = Number(itemForm.qty.$viewValue);
      var itemIndex = -1;
      $scope.shoppingCart.some(function(elem, index) {
        if (elem.id === item.id) {
          itemIndex = index;
          return true;
        }
      });
      if (itemIndex > -1) {
        var currItem = $scope.shoppingCart[itemIndex];
        currItem.quantity = newQuantity;
      } else {
        item.quantity = newQuantity;
        $scope.shoppingCart.push(item);
      }
    };
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
  <div style="width: 50%; margin-right: 2%; float: left;">
    <h3>Items</h3>
    <div ng-form="itemForm" ng-repeat="item in items">
      Name: {{item.name}}
      <br>Price: {{item.price | currency}}
      <br>Quantity:
      <input type="text" name="qty" ng-model="item.quantity" ng-model-options="{updateOn: 'submit'}" style="width: 50px;" />
      <br>
      <br>
      <button ng-click="addToCart(item, itemForm);">Add To Cart</button>
      <hr/>
    </div>
  </div>
  <div style="width: 44%; float: left;">
    <h3>Shopping Cart</h3>
    <div ng-repeat="item in shoppingCart">
      Name: {{item.name}}
      <br>Quantity: {{item.quantity}}
      <br>Total Price: {{(item.price * item.quantity) | currency}}
      <button ng-click="removeFromCart(item)">Remove</button>
      <br>
      <hr/>
    </div>
    <hr/>Total # Items: {{shoppingCart.length}}
    <br/>Grand Total: <span ng-bind="getCartTotal() | currency"></span>
  </div>
</div>

Hope this helps get you on the right track! Let me know if you have any questions =)

UPDATE:

Since you are passing your items to your view from your controller via asp.net MVC, you can inject the values directly from your view into your angular module. Here are a couple examples of how I've done it.

var poCreationApp = angular.module('myApp', []);
poCreationApp.value("defaultLocale", "@ViewBag.SelectedLocale");
poCreationApp.value("userInitials", "@ViewBag.UserInitials");
poCreationApp.controller("myCtrl", ["defaultLocale", "userInitials", "$scope", "$http", function (defaultLocale, userInitials, $scope, $http) {
    $scope.locale = defaultLocale;
    $scope.userInitials = userInitials;
    // ..... 
}]);

You can also pass through an entire view model like this:

@{
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    serializer.MaxJsonLength = Int32.MaxValue;
    var jsonModel = serializer.Serialize(Model);
}

<script>
    var app = angular.module("myApp", []);
    app.value("viewModel", @Html.Raw(jsonModel));
    app.controller("myCtrl", ["viewModel", "$scope", function (viewModel, $scope) {
        $scope.model = viewModel;
        $scope.items = $scope.model.items;
        // ......
    }]);
</script>
Community
  • 1
  • 1
mhodges
  • 10,938
  • 2
  • 28
  • 46
  • Thanks for this piece of code. This is really helpful. I am using angular with asp.net mvc by the way so I actually pass the list of items to the view from my controller. Do you think that it is better to have an angular function to retrieve that list of items in json format so I could use the ng-repeat feature? Or is there any other more appropriate option? – Rian Dec 05 '16 at 17:16
  • I use asp.net mvc as well with razor. You really have 2 options, you can get them via ajax, but if you already pass it through to the view, what you can do is you can inject the values into your angular module using `.value()` on `angular.module()`. I can post a couple of examples – mhodges Dec 05 '16 at 17:20
0

Your code gives you .find() is not a function because you are trying to use a jQuery method in a DOM element.

You need a jQuery object in order to use a jQuery method, and $event.currentTarget returns a DOM element, not a jQuery object.

If you have a variable containing a DOM element (btn in your case), you can cast it to a jQuery object in an easy way: $(yourDOMElement).

So you will be ok changing

btn.closest('.parentDiv').find('.item-id')

to

$(btn).closest('.parentDiv').find('.item-id')
Gerard Reches
  • 3,048
  • 3
  • 28
  • 39
0

If you don't want to include another version of jQuery, just use angular.element:

angular.element($event.currentTarget)