Scope issue in Javascript when writing to an array - javascript

BACKGROUND
I have a list of buyerNames and want the admin user to be able toggle their names. So far so good. Visually it works as expected. Admin user clicks on name and it's toggled on (background around the name and the items changes shade). Admin user can click and unclick names to his heart's desire.
CODE BELOW: I'm showing large sections of my code in case I'm messing something up in
a place where I don't think there's a problem.
<div class="headerSecondaryBg"> <!-- THE BACKGROUND LAYER - POSITION AND COLOR -->
<div class="buyerItems"> <!-- NUMBER OF BUYER ITEMS -->
<div class="item i1">42</div>
<div class="item i2">31</div>
<div class="item i3">57</div>
<div class="item i4">49</div>
<div class="item i5">16</div>
<div class="item i6">38</div>
<div class="item i7">24</div>
</div>
<div class="buyerNames"> <!-- BUYER NAMES -->
<div class="buyer b1">BUYERNAME 1 </div>
<div class="buyer b2">BUYERNAME 2 </div>
<div class="buyer b3">BUYERNAME 3 </div>
<div class="buyer b4">BUYERNAME 4 </div>
<div class="buyer b5">BUYERNAME 5 </div>
<div class="buyer b6">BUYERNAME 6 </div>
<div class="buyer b7">BUYERNAME 7 </div>
</div>
<div class="selectBuyer"> <!-- CREATES THE VISIBLE ON / OFF FOR THE TOGGLE AS PER DESIGN SPEC -->
<div class="selectBuyerOff b-on1"></div>
<div class="selectBuyerOff b-on2"></div>
<div class="selectBuyerOff b-on3"></div>
<div class="selectBuyerOff b-on4"></div>
<div class="selectBuyerOff b-on5"></div>
<div class="selectBuyerOff b-on6"></div>
<div class="selectBuyerOff b-on7"></div>
</div>
</div><!-- // END headerSecondaryBg -->
BACKGROUND
After the admin user has selected his buyers he clicks "show items" to reveal a hidden div below.
PROBLEM: Putting the toggled names into an array.
STEP 1:
Get buyerName (b-on1, b-on2 ... in this test example) and place in array.
$(".selectBuyer div" ).click(function(){
$(this).toggleClass("selectBuyerOn"); // show user that items are on or off
var all=$(this).attr('class');
console.log(all);
console.log = selectBuyerOff b-on1 selectBuyerOn.
EXACTLY WHAT WAS EXPECTED (considering I clicked on buyer 1)
STEP 2:
OK. Let's just have b-on1 and get rid of the other classes.
$(".selectBuyer div" ).click(function(){
$(this).toggleClass("selectBuyerOn"); // show user that items are on or off
var all=$(this).attr('class');
bbb=$(this).attr('class').split(' ')[1];
console.log(all);
console.log(bbb);
I get what's expected:
console.log(all) = selectBuyerOff b-on1 selectBuyerOn
console.log(bbb) = b-on1
STEP 3:
NOW let's put it into an array. (This goes immediately after the above code)
testArr=[];
testArr.push(all);
testArr.push(bbb);
console.log(testArr);
console.log = ["selectBuyerOff b-on1 selectBuyerOn", "b-on1"]
Now here's the problem - the array resets itself after every click.
I want the array to have b-on1 and b-on2 if the user selected those and
b-on1 and b-on2 if the user selected b-on1, b-on2, b-on3 (and then untoggled b-on3)
and yet the array is reset after every click.
How do I get this to work?
I tried removing var so that the variable would be in the global scope. I must be missing something simple.

When testArr=[] is executed, the testArr variable is assigned to a brand new empty array.
It sounds like you need to initialize this array just the once outside of the click handler, and then simply push values into the existing array within the handler. For example:
var testArr = [];
$(".selectBuyer div" ).click(function(){
$(this).toggleClass("selectBuyerOn"); // show user that items are on or off
var all=$(this).attr('class');
bbb=$(this).attr('class').split(' ')[1];
testArr.push(all);
testArr.push(bbb);
console.log(testArr);
/* ... */
});

Related

Hide particular div if all elements have the same class

I have a bunch of same elements on the page. All those elements have this particular structure:
<div id="9p" class="col s12 m6 l2 cardmaion globalcardclass">
<div class="card 9p">
<div class="card-content ">
<div class="row card-row">
<!-- a bunch of divs -->
<!-- a line of a card -->
<a data-category="here" class="hidden">
<div style="border-top:2px dotted gray;font-size:18px;background:ivory;margin-top:1px;"
id="belanimation" class="card-line waves-effect waves-green">
Some random text goes here <span class="card-line-q"> x 1</span><span
class="modspan "></span></div>
</a>
<!-- a line of a card -->
<a data-category="here" class="hidden">
<div style="border-top:2px dotted gray;font-size:18px;background:ivory;margin-top:1px;"
id="belanimation" class="card-line waves-effect waves-green">
Some random text goes here <span class="card-line-q"> x 2</span><span
class="modspan "></span></div>
</a>
<!-- a line of a card -->
<a data-category="here" class="hidden">
<div style="border-top:2px dotted gray;font-size:18px;background:ivory;margin-top:1px;"
id="belanimation" class="card-line waves-effect waves-green">
Some random text goes here <span class="card-line-q"> x 2</span><span
class="modspan "></span></div>
</a>
</div>
</div>
</div>
</div>
All the cards (example above) have common class = globalcardclass (it's only way to track that 'card elements')
All lines in a card could be (or not!) contains class = hidden
So I want a script to hide the whole card if all <a> elements (lines) have the class = hidden
If one or two elements (lines) do not have class hidden -- the card is shown.
I need to add another hide class to a particular card if all its children elements have class hidden.
Here I try something but It doesn't work at all. The hard part is... when the card element gets elements (lines) with class hidden that card must show up again...
Please, help.
<script>
$(document).ready(function () {
function FilterHere() {
var emptyCounter = 0
jQuery(".globalcardclass a").each(function () {
if ($(this).is('.hidden')) {
emptyCounter++
}
emptyCounter--
});
if (emptyCounter === 0) {
$(".globalcardclass").hide();
}
}
setTimeout(FilterHere, 2000);
});
</script>
UPD!
Here is new way to solve my problem:
jsfiddle.net/q6cekb18
But it also doesn't work...
UPD 2 (!)
Finally! I've got some progress!
$(".globalcardclass").filter(function(){
return $(this).find(".smoothmaion:visible").length == 0;
}).hide();
That code is working... All the card are hiding... But...
A card (.globalcardclass) is not showing up if 'line' element (.smoothmaion) becomes visible again... How to make it some sort of a... toggle?
Yeah... I can:
<script>
function Filtering()
{
$(".globalcardclass").filter(function(){
return $(this).find(".smoothmaion:visible").length == 0;
}).toggleClass( "hide_hide" )
};
</script>
But... When I use another button to filter different type of elements... That 'toggle' thing brings back that hidden element 'cause it has not change so ... toggle..
What can I do?

Retain scroll position when a button is toggled

I am working on an angularJS application which has a page where I display around 30 items using ng-repeat. In front of each item, there is a toggle button (enabled/disabled). With the current code that I have, I can toggle these items. But the problem is if I scroll down and toggle lets say item 25, then automatically it scrolls to the top of the page. If I now scroll down, I can see that the toggle actually took place.
So the requirement now is to make sure that the scroll position is retained after the toggle button is clicked.
Please see below the code that I have.
HTML
<div id="eventTypes" class="panel-body">
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
<div ng-if="spinner" class="spinner">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
</div>
JS
(function () {
'use strict';
angular.module('myApp').controller('itemsController', function ($scope, itemsService) {
var serviceError = function (errorMsg) {
console.log(errorMsg);
$scope.turnOffSpinner();
};
$scope.items = [];
$scope.item = {};
$scope.spinner = true;
$scope.toggleEnabled = function (item) {
$scope.turnOnSpinner();
itemsService.toggleEnabled(item)
.then(function () {
$scope.loaditems();
});
};
$scope.loaditems = function () {
itemsService.getitems().then(function (response) {
$scope.items = response.data;
}, serviceError);
$scope.turnOffSpinner();
};
$scope.turnOnSpinner = function () {
$scope.spinner = true;
};
$scope.turnOffSpinner = function () {
$scope.spinner = false;
};
$scope.loaditems();
});
}());
How this works right now is, once I click the toggle button, a spinner is enabled. Meanwhile the controller will call the itemService.toggleEnabled() method which does an ajax call to the server to just change the status of the item(enabled to disabled or vice-versa) in the backend. On successful change of the status and when the ajax call returns, the $scope.loadItems() method is called in the controller. This method will then do another ajax call to fetch the items (now with the updated status of the item that was toggled). The spinner is disabled and the data is then displayed on the UI.
When all of this is done, the page is scrolled to the top. This is annoying when I want to toggle an item which is way down in the list.
I want the page to be present at the same position when I clicked the toggle button of the corresponding item and not scrolling up to the top.
I am new to AngularJS and any help in this regard would be really helpful.
It looks like your spinner scheme is what's causing you problems:
...
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
...
<div ng-if="spinner" class="spinner">
...
Whenever you click your button, you are removing every single element in your ng-repeat from the DOM when you $scope.turnOnSpinner(). That's why it appears to jump to the top. It's not really jumping, there just aren't enough DOM elements to fill up the page, making the page so short that the scrollbar disappears (even if it's only for a second). Then when the spinner is done, your ng-repeat fills up the page with DOM elements again, resulting in your scroll position being lost.
So basically what you are trying to fix is a symptom of a less than ideal loading spinner implementation.
ng-if is a "brutal" way of hiding things in Angular. It's mostly meant to hide things for a longer period of time than "softer" directives like ng-show/ng-hide. One solution to your problem is to use ng-disabled on each one of your buttons to prevent the user from interacting with it while the spinner is active, rather than doing a hard removal of each element:
Before:
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
After:
<div ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
ng-disabled="spinner"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
Another solution, which I really like and use myself is this Angular module: https://github.com/darthwade/angular-loading
You can attach it to any element in the page and it will put a loading spinner over it and prevent you from interacting with it until your ajax or whatever is done.
If you don't like either of those, try putting your ng-repeat into a container that you can use to prevent interaction with your elements when the spinner is up:
<div class="container" ng-class="{'you-cant-touch-this': spinner}">
<div ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
</div>
Now you can style it in some way to prevent interaction without having to remove all those items from the DOM:
.you-cant-touch-this {
pointer-events: none;
}

I am misunderstanding a jQuery array

This code doesn't work
var next = $("#orders").find(".next");
if (next.length == 1) {
var address = $(next[0]).find(".directionsAddress");
var destination = $(address[0]).text();
}
It is suppose to find one div with a class of "next" that I know exists on the page, then within that one item of the result set array, there will be one div with a class name of directionsAddress. The "next" array is coming back with a length of 1, so it looks like the problem is with my $(next[0]).find because the address array is coming back as 0 length and I am making a syntax error of some sort that I don't understand.
are you looking to do something like this?
$(document).ready(function() {
alert($('#orders div.next:first div.directionsAddress:first').text());
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id = "orders">
<div class="next">
<div class="directionsAddress">THIS IS WHAT I WANT</div>
<div class="directionsAddress">This is not the one I want</div>
</div>
<div class="next">
<div class="directionsAddress">This is not the one I want</div>
</div>
<div class="next">
<div class="directionsAddress">This is not the one I want</div>
</div>
</div>

Checkboxes count selected checkboxes, sometimes returns an error

I am using an accordion, divided into 5 categories, showing several rows of checkboxes. When the visitor selects a checkbox, it returns the amount of selected checkboxes per category. Nothing is shown when none of the checkboxes are selected. E.g.
Category 1: 2 checkboxes selected
Category 2: 1 checkboxes selected
Category 3:
Category 4: 10 checkboxes selected
Category 5:
This works, so when the visitor adds another one in category 4 it will show "11 checkboxes selected". Also when one is turned off, it of course shows "9 checkboxes selected".
However, this works 95% of the time. When I randomly click the checkboxes, it sometimes returns an error in the log
Uncaught TypeError: undefined is not a function studiezoeker:487Request studiezoeker:487(anonymous function) studiezoeker:479v.event.dispatch ScriptResource.axd?d=dNZi6RW_20g0sp3isLHt5GoHGXYiqvO1u3-FU9wcgPXyQCu1rmFi0RuSIwIzaJjczNe7S8D3OTnoIX…:2o.handle.u
The counter in this case will be stuck at whatever it was. In this example, category 4 will be stuck at 10. However, when I now click another one, the results are correct again. 12 selected will be shown.
I've searched for hours, but I just can't find it.
The Javascript-part where it goes wrong. It goes wrong within the first 4 lines.
$(".filtercontainer .accordionitem").each(function () {
$(".beefup-body [type='checkbox']", this).change(function () {
var count = $(this).parent().parent().parent().delay(100).find($("[type='checkbox']:checked")).length;
console.log(count);
$(this).parent().parent().parent().siblings("h2").find(".checkCount").text(count + " geselecteerd");
if (count == 0) {
$(this).parent().parent().parent().siblings("h2").find(".checkCount").hide();
}
else {
$(this).parent().parent().parent().siblings("h2").find(".checkCount").show();
}
});
The HTML (stripped down all checkboxes and all categories, so it's easier to read
<div class="filtercontainer">
<div class="Accordion">
<div class="AccordionInner">
<article class="taxonomy accordionitem open" data-taxonomy="school" role="article">
<h2 class="beefup-head">school <span class="checkCount">14 selected</span></h2>
<div class="beefup-body" style="display: block;">
<div class="columncount">
<div class="taxa" title="ade08e3b-77bc-6478-97b6-ff00006ae40d" data-taxa-id="ade08e3b-77bc-6478-97b6-ff00006ae40d">
<input id="ade08e3b-77bc-6478-97b6-ff00006ae40d" type="checkbox" name="ctl00$Content$C022$cbFilter">
<label class="coverCheckBox" for="ade08e3b-77bc-6478-97b6-ff00006ae40d"><span></span></label>
<span class="filterName">One checkbox as an example</span>
<!-- More checkboxes below this one, upto 40 per category -->
</div>
<div class="taxa" title="afe08e3b-77bc-6478-97b6-ff00006ae40d" data-taxa-id="afe08e3b-77bc-6478-97b6-ff00006ae40d"></div>
</div>
</div>
</article>
<article class="taxonomy accordionitem" data-taxonomy="vorm" role="article">
<h2 class="beefup-head">vorm <span class="checkCount"></span></h2>
<div class="beefup-body" style="display: none;">
<div class="columncount">
<!-- Checkboxes in this category as well -->
</div>
</div>
</article>
<!-- More categories containing their own checkboxes here as well -->
</div>
</div>
</div>

Angular view not updating

I am quite new to Angular.js and think I am missing something small but important here.
To learn angular I am building this little panel to route video sources to destinations.
I have a list of sources and a list of destinations (each destination with 2 slots).
These will later be loaded from an API, but for now are defined in the js.
When I select one of the sources, the "selectedSource" var gets set with the clicked source.
When I than click a destination-slot is sets that slot's content with the "selectedSource" object.
The console log tells me that the thumb url of the slot has updated, but my html does not show the updated image. I've already messed around with "apply" altough I don't beleieve that is the way to go.
See my simplified code here:
http://jsfiddle.net/f4Tgf/
function app($scope, $filter) {
$scope.selectedSource = null;
$scope.sources = {
source1 : {id:'source1', thumbUrl:'http://lorempixel.com/100/100/sports/1/'},
source2 : {id:'source2', thumbUrl:'http://lorempixel.com/100/100/sports/2/'},
source3 : {id:'source3', thumbUrl:'http://lorempixel.com/100/100/sports/3/'}
}
$scope.destinations = {
dest1 : {id:'dest1', slots: {slot1 : $scope.sources.source2,slot2 : $scope.sources.source3} }
}
$scope.selectSource = function(source){
if($scope.selectedSource == source){
// toggle the selected source off if it is already selected
$scope.selectedSource = null;
}else{
$scope.selectedSource = source;
}
}
$scope.selectSlot = function(slot){
slot = $scope.selectedSource;
console.log(slot.thumbUrl);
//reset selected source
$scope.selectedSource = null;
}
}
HTML:
<body >
<div ng-app ng-controller="app" class="container-fluid">
<div class="row">
<!-- SOURCES -->
<div id="source-container" class="col-xs-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Sources</h3>
</div>
<div class="panel-body row">
<!-- Show all sources -->
<div ng-repeat="source in sources" ng-class="{selected: source==selectedSource}" ng-click="selectSource(source)" class="col-md-6 col-sm-12">
<div class="thumbnail">
<img class="img-responsive" src="{{source.thumbUrl}}" />
</div>
</div>
</div>
</div>
</div>
<!-- SOURCES -->
<!-- DESTINATIONS -->
<div id="sink-container" class="col-xs-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Destination</h3>
</div>
<div class="panel-body row">
<!-- Show all destinations -->
<div ng-repeat="destination in destinations" ng-class="{available: selectedSource!=null}">
<div class="thumbnail">
<div ng-repeat="slot in destination.slots" ng-click="selectSlot(slot)">
<img class="img-responsive" src="{{slot.thumbUrl}}" />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- END DESTINATIONS -->
</div>
</div>
</body>
(before you tell me to use services and so, please remember: this is a first try-learning project)
The problem is that you are trying to change the value parameter, slot, of the selectSlot() function instead of the destination's array of slots itself. Since you can have one ore more destinations slots from a list of destinations then the most probable solution would be to change selectSlot() to accept the current destinations array of slots and the key of the slot that you want to change.
Simply change this HTML code from
<div ng-repeat="slot in destination.slots" ng-click="selectSlot(slot)">
to
<div ng-repeat="(key, slot) in destination.slots" ng-click="selectSlot(destination.slots, key)">
and in your JAVASCRIPT selectSlot() definition should be
$scope.selectSlot = function(slots, key){
slots[key] = $scope.selectedSource;
//reset selected source
$scope.selectedSource = null;
}
See this UPDATE FIDDLE to view it in action.
Note: you can refer to AngularJS' documentation regarding ng-repeat's (key, value) synatx.
Seems this is a pure JavaScript problem.
In you selectSlot():
slot = $scope.selectedSource;
this operation does not actually assign $scope.selectedSource to one of $scope.destinations.dest1.slots as you expected.
You may want to do it this way:
slot.id = $scope.selectedSource.id;
slot.thumbUrl = $scope.selectedSource.thumbUrl;
That will work.
But please also note that in your case, you initialize $scope.destinations.dest1.slots as tow objects of $scope.sources, that means change the slot in $scope.destinations.dest1.slots also results in changing the corresponding one in $scope.sources. It will work fine if as you said, the destinations "will later be loaded from an API".

Categories