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.
Related
I want to use AOS.js on my site but I have no option to add the necessary data attributes to the DIVs of the page.
Here's the markup from the docs (https://michalsnik.github.io/aos/):
<div data-aos="fade-up" data-aos-duration="3000">
...
</div>
My markup looks like this:
<div class="aos-fade-up aos-duration-3000">
...
</div>
Is there any way to use AOS.js with only classes?
I found a similar question while researching: Having trouble adding aos.js using classes
But there is no answer to that.
Here's the code from the other question but that doesn't work:
$('.aos-fade-up').each(function(i) {
$(this).attr('data-aos', 'fade-up');
});
Any ideas?
You can give this snippet a try :
<script>
function ismatch(str){
var ret = null;
var tab = ['data-aos_', 'data-aos-delay_', 'data-aos-duration_', 'data-aos-easing_'];
Object.values(tab).forEach( function (value) {
if (String(str).match(value)){
ret = str.split('_');
return false;
}
});
return ret;
}
jQuery(document).ready(function ($) {
$('.some-class').each(function () {
var $this = $(this);
var tab = $this.attr('class').split(' ');
var keep;
Object.values(tab).forEach(function (item) {
var ello = ismatch(item)
if (ello !== null)
$this.attr(ello[0], ello[1]);
});
});
AOS.init();
});
</script>
Usage :
<div class="some-class data-aos_fade-down data-aos-delay_100 data-aos-duration_800"></div>```
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 have tried searching for what I am trying to accomplish, however I have not found what I am looking for.
I am looking to create a Next and Previous button inside the content of the Spry Accordion provided with Dreamweaver CS6. I have searched the SpryAccordion.js and found this code below:
Spry.Widget.Accordion.prototype.openNextPanel = function()
{
return this.openPanel(this.getCurrentPanelIndex() + 1);
};
Spry.Widget.Accordion.prototype.openPreviousPanel = function()
{
return this.openPanel(this.getCurrentPanelIndex() - 1);
};
So I attempted to do this with "#acc-step-1-next" being my "Next" button in Panel 1.
<script>
$(document).ready(function(){
$("#acc-step-1-next").click(function(){
Spry.Widget.Accordion.prototype.openNextPanel = function(){
return ('#Accordian1').openPanel(this.getCurrentPanelIndex() + 1);
};
});
});
</script>
I was wondering if doing it this way might make it easy! How would I go about applying this? Would this work or not?
Also, with the "Next" button, could I just make it ".acc-step-next" and use it universally, instead of individually assigning new ID's?
EDIT:
Sorry, yes I read your answer incorrectly. I have tried searching for the init property, however have had no success.
This is what starts in the Accordion JS file:
(function() { // BeginSpryComponent
if (typeof Spry == "undefined") window.Spry = {}; if (!Spry.Widget) Spry.Widget = {};
Spry.Widget.Accordion = function(element, opts)
{
this.element = this.getElement(element);
this.defaultPanel = 0;
this.hoverClass = "AccordionPanelTabHover";
this.openClass = "AccordionPanelOpen";
this.closedClass = "AccordionPanelClosed";
this.focusedClass = "AccordionFocused";
this.enableAnimation = true;
this.enableKeyboardNavigation = true;
this.currentPanel = null;
this.animator = null;
this.hasFocus = null;
this.previousPanelKeyCode = Spry.Widget.Accordion.KEY_UP;
this.nextPanelKeyCode = Spry.Widget.Accordion.KEY_DOWN;
this.useFixedPanelHeights = false;
this.fixedPanelHeight = 0;
Spry.Widget.Accordion.setOptions(this, opts, true);
if (this.element)
this.attachBehaviors();
};
Which I added this after, but still no luck:
var acc_next = document.getElementById("acc-step-next");
var acc_prev = document.getElementById("acc-step-prev");
$("acc_next").click(function(){
accordion.openNextPanel();
});
$("acc_prev").click(function() {
accordion.openPreviousPanel();
});
I have never worked with Spry.Widget.Accordion, but I would try something like the following.
Search for the code, where your accordion is initialized, it should look something like that:
var accordion = new Spry.Widget.Accordion("Accordian1",{});
And add this just below:
$(".acc-step-next").click(function(){
accordion.openNextPanel();
});
Together it could look something like that:
<script type="text/javascript">
$(document).ready(function(){
var accordion = new Spry.Widget.Accordion("Accordian1",{});
// Add a click handler to all buttons with the class 'acc-step-next' (yes you can do that)
$(".acc-step-next").click(function(){
// when the button is clicked, call the openNextPanel method of the accordion instance we saved above
accordion.openNextPanel();
});
});
</script>
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.
When a todo is created,I push it onto this array:
$scope.$on('todo:created',function(event,todo){
$scope.model.todos.push(todo);
});
I am trying to delete a todo using the $scope.$on functionality like this:
//from a child scope for the item
$scope.actions.remove = function remove()
{
TodoService.delete($scope.model.todo);
$scope.$emit('todo:deleted',$scope.model.todo);
};
//from a parent of the scope which has ng-repeat:
$scope.$on('todo:deleted',function(event,todo){
for(var i =0;i<$scope.model.todos.length;i++)
{
if(todo._id === $scope.model.todos[i]._id)
{
console.log(i);
$scope.model.todos.splice(i,1);
break;
}
}
});
I find that this seemingly normal code causes a lot of issues:
1)last item gets deleted correctly
2)when deleting the penultimate item,the last item gets deleted
3)all other items are unresponsive on delete
I find that the UI renders with the correct items deleted on page reload.
I have tried changing the code to:
$scope.$on('todo:deleted',function(event,todo){
$scope.model.todos = $.grep($scope.model.todos, function (todoItem, i) {
if (todoItem._id === todo._id) {
console.log(i);
return false;
} else {
return true;
}
});
});
The entire code can be found here on github
EDIT:
My code uses ng-repeat like this:
<section class="ui three column doubling page grid">
<div class="column" ng-repeat="todo in model.todos track by $index">
<todo-item value="todo"></todo-item>
</div>
</section>
UPDATE:
I have tried to accomplish the delete using this code:
var onItemDeleted = function onItemDeleted(todo){
var todos = $scope.model.todos;
var checkIndex = function checkIndex(t){
return t._id !== todo._id;
};
todos = todos.filter(checkIndex);
//$scope.model.todos = todos;
};
$scope.$on('todo:deleted',function(event,todo){
$scope.$apply(onItemDeleted(todo));
});
var onTodoAdded = function onTodoAdded(todo){
var todos = $scope.model.todos;
todos.push(todo);
};
$scope.$on('todo:created',function(event,todo){
$scope.$apply(onTodoAdded(todo));
});
In both these cases,I get the error:
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.3.15/$rootScope/inprog?p0=%24digest
at REGEX_STRING_REGEXP (angular.js:66)
at beginPhase (angular.js:14823)
at Scope.$get.Scope.$apply (angular.js:14567)
at app.js:218
at Scope.$get.Scope.$emit (angular.js:14715)
at app.js:264
at processQueue (angular.js:13251)
at angular.js:13267
at Scope.$get.Scope.$eval (angular.js:14469)
at Scope.$get.Scope.$digest (angular.js:14285)
I have tried replacing the data by replacing the data from the service like this:
$scope.$on('todo:deleted',function(event,todo){
var todosPromise = TodoService.get();
todosPromise.then(function(data){
$scope.model.todos = data;
});
});
And I have changed the event code to fire only after the delete operation is completed:
$scope.actions.remove = function remove(id)
{
TodoService.deleteItem($scope.model.todo)
.then(function(){
$scope.$emit('todo:deleted',$scope.model.todo);
})
.catch(function(err){
console.log(err,err.stack);
});
};
I find that the data from the service or upon splicing is obtained correctly but the wrong element is pulled by ng-repeat,is it because I am using track by $index.
I faced some issues with splice and used $grep to fix those issues.
Can you try the below code:
$scope.$on('todo:deleted',function(event, todo) {
$scope.model.todos = $.grep($scope.model.todos, function (todoItem, i) {
if (todoItem._id === todo._id) {
return false;
console.log(i);
} else {
return true;
}
});
});