I have a controller in angular where there is a huge form split in cards. So each time a user picks a title, the card for that section shows (ex. name parts, address parts, contact data, college data, work data, etc).
The relevant code for doing that with only two sections is this:
angular.module('controllers', [])
.controller('EditUserController', function($scope, $state) {
$scope.mustShowName = false;
$scope.mustShowContact = false;
$scope.toogleShowContact = function() {
if ($scope.mustShowContact) {
$scope.mustShowContact = false;
} else {
$scope.mustShowContact = true;
}
};
$scope.toogleShowName = function() {
if ($scope.mustShowName) {
$scope.mustShowName = false;
} else {
$scope.mustShowName = true;
}
};
});
But there are various cards. There is a way of refactoring that in something like this?:
$scope.toogleSection = function(section) {
if (section) {
section = false;
} else {
section = true;
}
};
...
$scope.toogleSection($scope.mustShowName);
If I try it, it doesn't work and doesn't throw errors, so I think it is just copying the variable and not referencing the original one.
When you ask for $scope.mustShowName you just get the value and not the reference of the property - ie true or false. Instead pass the section name as a string, and refer to the property in the scope using the name.
btw - A better idea would be to create a directive that encapsulates the behavior, and will help you stays DRY.
$scope.toogleSection = function(sectionName) {
if ($scope[sectionName]) {
$scope[sectionName] = false;
} else {
$scope[sectionName] = true;
}
};
$scope.toogleSection('toogleShowName');
You can refactor it so you can send the name of the property as a parameter to the function, like this:
$scope.toggleSelection = function(sectionName) {
$scope[sectionName] = !$scope[sectionName];
};
You can use a ng-if="show" for the detailed part of the card. Then do a ng-click="show = !show". BAM! you have a toggle on touch that will show and hide whatever you put the ng-if on. Here is a example from a app I made.
<div class="item" ng-show="directions">
<!--directions go here-->
</div>
<div style="text-align: center; background-color:#284f9a;" class="item" ng-click="directions = !directions">
<p style="color: white;" ng-if="directions == false">See Directions:</p>
<p style="color: white;" ng-if="directions == true">Close Directions:</p>
</div>
with this I can show and hide the directions and change what the show/hide button says.
This also works really well with ng-repeat and only toggles the item you click on.
Related
Hey guys I am currently creating a newsletter popup.
I'm wanting to hide the div after the close button is selected using a cookie. The code snippet I've taken does include some code to try and achieve this but doesn't seem to work for me. Anyone know a solution?
JS
var delay = 0; //in milliseconds
jQuery(document).ready(function($){
setTimeout(function(){
showNewsletterPopup();
}, delay);
jQuery('.popup-close').click(function(){
$('.newsletter-overlay').hide();
});
});
function showNewsletterPopup(){
jQuery('.newsletter-overlay').show();
}
function onLoad() {
var showDiv;
if(localStorage.getItem("showDiv") == null) {
showDiv = true;
}
else {
showDiv = localStorage.getItem("showDiv")
}
if (showDiv) {
document.getElementById('newsletter-overlay').style.display = 'block';
}
else {
document.getElementById('newsletter-overlay').hide();
}
}
function onClose() {
document.getElementById('newsletter-overlay').remove();
localStorage.setItem("showDiv", false);
}
HTML
<div class="newsletter-overlay">
<div id="newsletter-popup">
<img src="/wp-content/uploads/static/TLTX.svg">
<div class="col1">
<div class="newsletter-in">
<h3>Take 10% off your first purchase</h3>
<p class="modalp">Join our Tribe! Our mates get the best rates. Every $1 spent will earn you 1 point. Be the first to know about new arrivals. Receive 10% off your first order! See more on our Tribe page
[wc_reg_form_bbloomer]
</div>
</div>
</form>
</div>
</div>
</div>
the provided code is a bit messy... there are some unused functions and a lot of noise. however, here is my proposal:
$(document).ready(function($) {
const $newsletterPopup = $('#newsletter-popup');
const $newsletterOverlay = $('.newsletter-overlay');
const $popupCloseLink = $('.popup-close');
let showDiv = JSON.parse(localStorage.getItem("showDiv"));
if (showDiv === null) {
showDiv = true;
}
if (showDiv) {
$newsletterOverlay.show();
$newsletterPopup.show();
} else {
$newsletterOverlay.hide();
$newsletterPopup.hide();
}
$popupCloseLink.click(function() {
$newsletterOverlay.hide();
$newsletterPopup.hide();
localStorage.setItem("showDiv", false);
});
});
I am very new to Javascript.
I am trying to write this baby jQuery plugin that I will use to make dropdown lists. What I am failing to achieve (beyond things that I do not notice) is to neatly exit or deactivate my active instance as I click on another instance. I tried to illustrate my problem in the following fiddle (keeping the structure I am using):
https://jsfiddle.net/andinse/m0kwfj9d/23/
What the Javascript looks like:
$(document).ready(function() {
$.fn.activator = function() {
var Activator = function(el) {
this.html = $('html');
this.el = el;
this.is_active = false;
this.initialize();
};
Activator.prototype.initialize = function() {
var self = this;
self.el.on('click', function(e) {
if (self.is_active === false) {
self.toggle('activate');
} else {
self.toggle('deactivate');
}
});
};
Activator.prototype.toggle = function(action) {
var self = this;
if (action === 'activate') {
console.log('activating ' + self.el[0].className);
self.is_active = true;
self.el.addClass('red');
self.html.on('click', function(e) {
if (e.target != self.el[0]) {
self.toggle('deactivate');
}
});
}
if (action === 'deactivate') {
console.log('deactivating ' + self.el[0].className);
self.is_active = false;
self.el.removeClass('red');
self.html.off('click');
}
};
if (typeof this !== 'undefined') {
var activator = new Activator(this);
}
return this;
};
$('.a').activator();
$('.b').activator();
$('.c').activator();
});
My idea was:
To watch for clicks on html as soon as the instance is active (thus ready to be deactivated). On click, to check if the event.target is the same as the active instance. If not, to deactivate this instance.
To stop watching for clicks as soon as the instance is inactive. So that we're not doing unnecessary work.
When it is set like this, it seems to work for only one cycle (click on A activates A then click on B activates B and deactivates A then click on C activates C but doesn't deactivate B).
If I get rid of the "self.html.off('click')" it seems to work kind of ok but if I look at the log I can see the "toggle" function is sometimes triggered multiple times per click. There must be a cleaner way.
Any piece of help greatly appreciated.
With your logic, when clicking any element you should deactivate any current activated element. Either do it globally:
$('.your_activation_class').removeClass('.your_activation_class');
or in some parent scope
$('some_parent_selector .your_activation_class').removeClass('.your_activation_class');
I am building a sort of (faux) loader in Angular. Currently, I have this:
const app = angular.module('app', []);
app.controller('loaderCtrl', ($scope, $timeout) => {
let loading = $scope.loading,
loaded = $scope.loaded;
$scope.reset = () => {
$timeout(() => {
loading = false;
loaded = false;
console.log(loaded);
}, 500);
}
});
HTML:
<main ng-app="app">
<div ng-controller="loaderCtrl as loader" >
<div class="loader" ng-class="{ '-loading' : loader.loading === true, '-loaded' : loader.loaded === true }"></div>
<button ng-click="loader.loading = true;">loading</button>
<button ng-click="loader.loaded = true; reset();">loaded</button>
</div>
</main>
CodePen: http://codepen.io/tomekbuszewski/pen/WrXXdp
My problem is, both loading and loaded aren't being set up for my view, so the classes are permanently there. What can I do?
So, this is a problem of scope. Basically when you do this
let loading = $scope.loading,
loaded = $scope.loaded;
You get the "value" of the variables inside Angular scope. Therefore Angular does not know anything about changes made to those
The fix is simple, don't do that, but instead
$scope.reset = () => {
$timeout(() => {
$scope.loading = false;
$scope.loaded = false;
}, 500);
}
Why not using an object and change its content? It is possible to do that as #beaver pointed out, but then you have another problem, you need to trigger the digest cycle yourself via $apply. And somewhere in your code, you might accidentally change the content of the object and it might affect other part of the system
Having said that I do not know Babel and so I worked on the JS compiled version, I noticed that you assigned loader.loading and loader.loaded to variables and then used those "references" in $timeout function.
As in javascript
Primitives are passed by value, Objects are passed by "copy of a
reference"
you have to use $scope.loader.loading and $scope.loader.loaded
app.controller('loaderCtrl', function ($scope, $timeout) {
$scope.loader = {};
var loading = $scope.loader.loading, loaded = $scope.loader.loaded;
$scope.reset = function () {
$timeout(function () {
$scope.loader.loading = false;
$scope.loader.loaded = false;
}, 500);
};
});
Here I forked your CodePen: http://codepen.io/beaver71/pen/wMPprm
Below in my list, one of the divs at the bottom has a removePortfolio function. This function's job is to activate the ng-hide="tickerRemoved" but only for that 1 list item, not all the list items.
HTML Gist: https://gist.github.com/leongaban/cf72e5d0229155dd011f
Directive Gist: https://gist.github.com/leongaban/22a8feb9dbeea0b90135
<ul ng-show="loadingTickersDone" class="tickers-list">
<li class="ticker-li"
ng-repeat="ticker in tickers"
ng-hide="tickerRemoved"
ng-class="{'selected':toggleTicker.item == $index}"
ng-mouseleave="hideTickerOptions()">
<div class="ticker"
ng-click="toggleTicker.item = $index;
selectTicker(ticker.ticker);
revealTickerOptions()">
{{ticker.ticker}}
</div>
<div class="add-to-portfolio"
ng-show="tickerOptions"
ng-mouseleave="hideTickerOptions()">
<div ng-show="addOption"
ng-click="addPortfolio(ticker.ticker)">+ Portfolio</div>
<div ng-show="removeOption"
ng-click="removePortfolio(ticker.ticker)">- Portfolio</div>
</div>
</li>
</ul>
Here is the remove function in the directive:
var vs = $scope;
vs.removePortfolio = function(ticker) {
this.tickerOptions = false;
ApiFactory.deleteWatchList(ticker).then(function(data) {
showMessage(ticker+' removed from portfolio!', data.data.status);
this.tickerRemoved = true;
});
};
I get an error with this.tickerRemoved = true; I think this is because the scope is lower in the chain?
For example, I'm using this in this function and it works fine because the function is higher in the markup/scope:
vs.revealTickerOptions = function() {
this.tickerOptions = true;
if (tickerView === 'all') {
this.addOption = true;
this.removeOption = false;
}
else if (tickerView === 'port') {
this.addOption = false;
this.removeOption = true;
}
};
How would I remove just the 1 <li class="ticker-li" item when clicking the removePortfolio() function?
ng-hide="tickerRemoved" should be ng-hide="ticker.tickerRemoved" since tickerRemoved is a property of a specific ticker.
Same with ng-show="tickerOptions"... should be ng-show="ticker.tickerOptions" from the looks of it.
ng-click="removePortfolio(ticker.ticker)"> should be ng-click="removePortfolio(ticker)"> since you probably want to pass the entire ticker object.
After that, you will need to update your remove ticker function, something like this should work:
vs.removePortfolio = function(tickerObject) {
var ticker = tickerObject.ticker
tickerObject.tickerOptions = false;
ApiFactory.deleteWatchList(ticker).then(function(data) {
showMessage(ticker+' removed from portfolio!', data.data.status);
tickerObject.tickerRemoved = true;
});
};
As a general observation, it looks like you are leaning on this too much. this can be a very confusing keyword and should only be used (in my opinion) when there is both a good reason to do so and doing so will not cause confusion during later code maintenance.
So I am working in Knockout and I have a view model that is contains a single observable:
function rvwHistoryViewModel() {
if (document.getElementById("CCHPISentence").innerHTML != "") {
rvwHistory = ko.observable(document.getElementById("CCHPISentence").innerHTML);
}
else {
rvwHistory = ko.observable("crap");
}
}
Initially in the code above the CCHPISentence.innerHTLM == "" so the bindings are applied and my element reads "crap as it should".
However, I call this same viewModel later and at this point the CCHPISentence.innerHTLM == "some thing". I debug and see that rvwHistory is indeed set to "some thing", but the value on the screen still reads "crap".
What am I doing wrong? Thanks in advance to anybody that can help me here, I have been staring at this one a while.
This is an example of how your ViewModel can be. It contains an input and an example of a computed.
function rvwHistoryViewModel() {
this.CCHPISentence = ko.observable("");
this.rvwHistory = ko.computed(function() {
if (this.CCHPISentence() == "") {
return "crab"; // nice little animal
} else {
return this.CCHPISentence();
}
}, this);
}
ko.applyBindings(new rvwHistoryViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
CCHPISentence:
<input data-bind="value:CCHPISentence" type="text"/>
<br/>rvwHistory:<span data-bind="text:rvwHistory"></span>
tip, follow the excellent tutorial: http://learn.knockoutjs.com/