AngularJS with jQuery for selecting parent element - javascript

I'm posting my html and directive code. I have two spans, which I have attached ng-click to. On ng-click I want to check the classes of their parents parent item (.parent-portlet), but what I have so far is not working correctly as all of the parent portlets get selected instead of only the current one.
<div class="parent-portlet {{portlet.class}} {{portlet.class2}}" ng-mouseenter="hoverIn($event)" ng-mouseleave="hoverOut($event)">
<div class="portlet-titlebar" ng-click="toggleCollapsed($event)">
<span class="remove" ng-click="removePortlet(portlet)">
Remove
</span>
<span class="add-back" ng-click="addPortlet(portlet)">
Add Back
</span>
</div>
</div>
this is what I have in my directive:
scope.removePortlet = function(portlet) {
var port = $('.remove').parent().parent();
port.addClass('removed');
port.addClass('edit-portlet');
};
scope.addPortlet = function(portlet) {
var port = $('.add-back').parent().parent();
if (portlet.hasClass('removed')) {
port.removeClass('removed');
port.removeClass('edit-portlet');
}
};
The problem with this code is that var portlet catches all of the portlets(parents) and I want to catch only the one related to the click action. How can I achieve that? I tried to pass this to my jquery select like so:
var portlet = $('.add-back', this).parent().parent();
but that didn't work. Any ideas how this can be achieved?
Thanks in advance.

Pass in the $event to the ng-click like this:
<span ng-click="removePortlet($event)"></span>
Then, just modify your code like this:
scope.removePortlet = function(ev) {
var port = $(ev.target).parent().parent();
port.addClass('removed');
port.addClass('edit-portlet');
};
scope.addPortlet = function(ev) {
var port = $(ev.target).parent().parent();
if ($(ev.target).hasClass('removed')) {
port.removeClass('removed');
port.removeClass('edit-portlet');
}
};
Grabbing the "event's target" will ensure that you are only addressing the item that was actually clicked. And note that angular uses $event to pass the event to a function.
I would also clean it up a bit combining the class modification lines and targeting the specific parent (in case you ever modify the DOM) using .closest():
scope.removePortlet = function(ev) {
var port = $(ev.target).closest('.parent-portlet');
port.addClass('removed, edit-portlet');
};
scope.addPortlet = function(ev) {
var port = $(ev.target).closest('.parent-portlet');
if (portlet.hasClass('removed')) {
port.removeClass('removed, edit-portlet');
}
};

You can inject the $element to your controller, then, use it as the context for the $ selector.
angular.module('app', []).controller('ctrl', function($scope, $element) {
$scope.addPortlet = function(portlet) {
console.log($('.add-back', $element));
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class="parent-portlet">
<div class="portlet-titlebar" ng-click="toggleCollapsed($event)">
<span class="remove" ng-click="removePortlet(portlet)">
Remove
</span>
<span class="add-back" ng-click="addPortlet(portlet)">
Add Back
</span>
</div>
</div>
</div>
For more info about it

Use ng-class instead. Here's an example of changing background colors with classes:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.portlet = {
classes: {
"parent-portlet": true,
"edit-portlet": false,
"removed": false
}
}
$scope.removePortlet = function(portlet) {
portlet.classes.removed = true;
portlet.classes["edit-portlet"] = true;
};
$scope.addPortlet = function(portlet) {
portlet.classes.removed = false;
portlet.classes["edit-portlet"] = false;
};
});
/* Put your css in here */
.parent-portlet {
background-color: #ccc;
padding: 20px;
}
.parent-portlet.removed {
background-color: #c00;
}
.parent-portlet button {
padding: 10px;
margin: 0 10px;
}
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-class="portlet.classes">
<div class="portlet-titlebar">
<button class="remove" ng-click="removePortlet(portlet)">
Remove
</button>
<button class="add-back" ng-click="addPortlet(portlet)">
Add Back
</button>
</div>
</div>
</body>
</html>

Related

How to make JavaScript Event Listeners less repetitive?

I am having some trouble thinking new ways to optimize this block of code. Now it looks too repetitive and long. I can write functions separately from EventListener but it would only make more lines of code and make it even longer.
let modalIntro = document.getElementById('modal-intro');
let buttonIntro = document.getElementById('button-intro');
let close = document.getElementsByClassName('close')[0];
buttonIntro.addEventListener ('click', function(){
modalIntro.classList.remove("out");
modalIntro.classList.add("in");
});
close.addEventListener('click', function (){
modalIntro.classList.add("out");
});
let modalWork = document.getElementById('modal-work');
let buttonWork = document.getElementById('button-work');
let close1 = document.getElementsByClassName('close')[1];
buttonWork.addEventListener ('click', function(){
modalWork.classList.remove("out");
modalWork.classList.add("in");
});
close1.addEventListener('click', function (){
modalWork.classList.add("out");
});
let modalAbout = document.getElementById('modal-about');
let buttonAbout = document.getElementById('button-about');
let close2 = document.getElementsByClassName('close')[2];
buttonAbout.addEventListener ('click', function(){
modalAbout.classList.remove("out");
modalAbout.classList.add("in");
});
close2.addEventListener('click', function (){
modalAbout.classList.add("out");
});
let modalContact = document.getElementById('modal-contact');
let buttonContact = document.getElementById('button-contact');
let close3 = document.getElementsByClassName('close')[3];
buttonContact.addEventListener ('click', function(){
modalContact.classList.remove("out");
modalContact.classList.add("in");
});
close3.addEventListener('click', function (){
modalContact.classList.add("out");
});
Any help would be much appreciated.
Seeing as you have a common naming pattern, and assuming the close button is child of each modal, you could do something like this:
Note I added modal.classList.remove('in'); to the close button
Solution
function bindModals(modals) {
modals.forEach(name => {
let modal = document.getElementById(`modal-${name}`)
let button = document.getElementById(`button-${name}`)
let close = modal.querySelector('.close');
button.addEventListener ('click', function(){
modal.classList.remove('out');
modal.classList.add('in');
});
close.addEventListener('click', function (){
modal.classList.remove('in');
modal.classList.add('out');
});
});
}
bindModals(['intro', 'work', 'about', 'contact'])
.out {
display: none;
}
.in {
display: block;
border: 1px solid red;
height: 200px;
width: 400px;
}
<section>
<button id="button-intro">Intro</button>
<button id="button-work">Work</button>
<button id="button-about">About</button>
<button id="button-contact">Contact</button>
</section>
<section id="modal-intro" class="out">
<button class="close">Close</button>
<p>Intro Modal</p>
</section>
<section id="modal-work" class="out">
<button class="close">Close</button>
<p>Work Modal</p>
</section>
<section id="modal-about" class="out">
<button class="close">Close</button>
<p>About Modal</p>
</section>
<section id="modal-contact" class="out">
<button class="close">Close</button>
<p>Contact Modal</p>
</section>
You can use a function to get element Id to make it more shorter:
function getElem(el) {
return document.getElementById(el);
}
Now remove all below code:
let modalIntro = document.getElementById('modal-intro');
let buttonIntro = document.getElementById('button-intro');
let modalWork = document.getElementById('modal-work');
let buttonWork = document.getElementById('button-work');
let modalContact = document.getElementById('modal-contact');
let buttonContact = document.getElementById('button-contact');
– and replace variable with getElem('elem-id'), e.g given below
getElem('modal-intro').classList.remove("out");
getElem('modal-intro').classList.add("in");
Similarly you can make common function to get Element by class and add event listener.
Without having any HTML I am not sure if this is going to be perfectly accurate but I can try something.
I see each modal behaves in the same way... I would add an attribute to each button that indentifies which modal it activates, and a class so we can refer to all of them:
<input type="button" value="intro" class="modalButton" id="button-intro" data-activates="modal-intro">
<input type="button" value="work" class="modalButton" id="button-work" data-activates="modal-work">
<input type="button" value="about" class="modalButton" id="button-about" data-activates="modal-about">
<input type="button" value="contact" class="modalButton" id="button-contact" data-activates="modal-contact">
Now we can refer to each and action with this line of code:
document.getElementsByClassName("modalButton").forEach(function(btn) {
btn.addEventListener ('click', function() {
var modal = document.getElementById(btn.getAttribute("data-activates"));
modal.classList.remove("out");
modal.classList.add("in");
});
});
Assuming the buttons are direclty inside the modal like this:
<!-- Example of a modal with a close button -->
<div id="modal-intro" class="modal out">
<input type="button" class="close" value="close">
<!-- content -->
</div>
We can get to the modal by moving up in the DOM tree:
document.getElementsByClassName("close").forEach(function(closeBtn) {
closeBtn.addEventListener ('click', function(){
// In this case the modal is the button's parent
closeBtn.parentElement.classList().remove("in");
closeBtn.parentElement.classList().add("out");
// P.S I also suppose you want to remove the "in" class
});
});
Obiouvsly, if the button is deeper inside the modal, you just need to call the 'parentElement' property till you get to it.
Here's another approach that uses the fact that you have the same structure times three.
This would be much shorter if I had used jQuery, but I kept it vanilla since you don't seem to be using it:
document.querySelectorAll("nav a").forEach(a => a.addEventListener("click", function(e) {
e.preventDefault(); // don't navigate to href
// hide all modals, i.e. remove the one potentially still open
document.querySelectorAll("#modals > div").forEach(modal => modal.classList.remove("in"));
// finally, show modal
document.getElementById(this.dataset.modal).classList.add("in");
}));
document.querySelectorAll("#modals > div").forEach(modal =>
// for each modal, grab its close button
modal.querySelector(".close").addEventListener("click", function(e) {
e.preventDefault(); // don't navigate to href
modal.classList.remove("in"); // and hide the modal
})
);
#modals>div {
position: fixed;
width: 50%;
left: 25%;
box-sizing: border-box;
border: 1px solid black;
padding: 0.5em;
top: -300px;
transition: .5s
}
#modals .close {
float: right
}
.in {
top: 50px !important;
}
<nav>
<a data-modal="intro" href="">Intro</a>
<a data-modal="work" href="">Work</a>
<a data-modal="about" href="">About</a>
</nav>
<div id="modals">
<div id="intro">X
<p>Intro modal</p>
</div>
<div id="work">X
<p>Work modal</p>
</div>
<div id="about">X
<p>About modal</p>
</div>
</div>
By pointing the code to the modal via the menu link's data-modal, I no longer have to use any element-specific code at all, resulting in ideally reusable code.
Easiest would be to simply create a function:
function MakeModal(modalId, buttonId, closeId)
let modal = document.getElementById(modalId);
let button = document.getElementById(buttonId);
let close = document.getElementsById(closeId);
button.addEventListener ('click', function(){
modal.classList.remove("out");
modal.classList.add("in");
});
close.addEventListener('click', function (){
modal.classList.add("out");
});
}
Which you can then invoke:
MakeModal('modal-intro', 'button-into', 'close-intro');
MakeModal('modal-about', 'button-about', 'close-about');
MakeModal('modal-contact', 'button-contact', 'close-contact');
That's just programming basics: DRY.
Do note that you need to add ID's to your close-buttons instead of classes (or you can rewrite it to let close = modal.find('.close'); or something and in which case you can get rid of the third argument closeId). And if you adhere strictly to the naming convention used here you could even simplify the multiple MakeModal(...) calls:
['intro', 'about', 'contact'].forEach(function(e) {
MakeModal('modal-' + e, 'button-' + e, 'close-' + e);
});
Or, if you got rid of the third argument and used the .find(...) suggestion:
['intro', 'about', 'contact'].forEach(function(e) {
MakeModal('modal-' + e, 'button-' + e);
});
I'm no jQuery expert by any means but if I'm not mistaken
modal.classList.remove("out");
modal.classList.add("in");
can be written as:
modal.classList.switchClass("out", "in");

Is there a way to hide() unmatched search elements using mark.js?

Is there a way to hide() unmatched search elements using mark.js? After reading the documentation I initially thought that I could use the "noMatch" option to write a custom function to hide the parent div of unmatched text, but it's not working as hoped for.
For example, if I am using mark.js to search and highlight "Lorem" in the following:
<div class="panel-body context">
<h2>Lorem</h2>
<div>
<p>ipsum</p>
<p>lorem</p>
</div>
<h2>ipsum</h2>
<div>
<p>ipsum</p>
<p>lorem</p>
</div>
</div>
How can I get it to only return the matched elements like so?
<div class="panel-body context">
<h2><span="highlight">Lorem</span><h2>
<div>
<p><span="highlight">lorem</span></p>
</div>
<div>
<p><span="highlight">lorem</span></p>
</div>
</div>
Here's my current code block:
$(function() {
var mark = function() {
// Read the keyword
var keyword = $("input[name='search-keyword']").val();
// Determine selected options
var options = {
"element": "span",
"className": "highlight",
"separateWordSearch": false,
"noMatch": function(term) {
term.hide(); // WHERE I HOPED I COULD ADD HIDE LOGIC
}
}
// Remove previous marked elements and mark
// the new keyword inside the context
$(".panel-body context").unmark({
done: function() {
$(".panel-body context").mark(keyword, options);
}
});
};
$("input[name='search-keyword']").on("input", mark);
});
EDIT Here's my altered jsfiddle from the mark.js examples for a better example of what I'm attempting to do. Thanks for any assistance ahead of time!
$(function() {
var mark = function() {
// Read the keyword
var keyword = $("input[name='keyword']").val();
var keyword2 = $("input[name='keyword2']").val();
// Remove previous marked elements and mark
// the new keyword inside the context
$(".context").unmark({
done: function() {
$(".context").mark(keyword).mark(keyword2, {className: 'secondary'});
$("#binddata").text(keyword);
}
});
};
$("input[name^='keyword']").on("input", mark);
});
mark {
padding: 0;
background-color:yellow;
}
mark.secondary {
padding: 0;
background-color: orange;
}
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/superhero/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/mark.js/8.6.0/jquery.mark.min.js"></script>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<input type="text" name="keyword"><input type="text" name="keyword2">
<span id="binddata">Bind Character</span>
<div class="context">
The fox went over the fence
<h2>Lorem<h2>
<div>
<p>ipsum</p>
<p>lorem</p>
</div>
<h2>ipsum</h2>
<div>
<p>ipsum</p>
<p>lorem</p>
</div>
</div>

Invoke function only on the selected ng-repeat element

Currently I am trying to fetch some data using JSON and have structured it over my website using ng-repeat.
The problem is that on ng-click, on any newly created element the function invokes on each element as well. I have also tried using the this operator but it doesnt seem to work on angularjs.
I have created a dummy problem similar to mine.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<div ng-repeat="aplhabet in word">
<button style="width:50px; margin: 5px" ng-click="addDiv()">
<b>{{aplhabet}}</b>
</button>
<section ng-show="newdiv">functionInvoked</section>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope){
$scope.word = 'STRINGDEMO';
$scope.addDiv = function () {
$scope.newdiv = true;
}
}
</script>
</body>
</html>
As you might notice whenever you click on any button the function runs for each element. I need help to understand how to pass any identifier in this function so that the function is invoked only on the element clicked.
you need an object array to achieve this. simply use a for loop to convert this to an array of object.
for(key in $scope.word){
$scope.wordArr.push({
letter : $scope.word[key],
newDiv : false
})
}
use the new array as ng-repeat. to print letter use <b>{{aplhabet.letter}}</b>
<div ng-repeat="aplhabet in wordArr">
<button style="width:50px; margin: 5px" ng-click="addDiv(aplhabet)">
<b>{{aplhabet.letter}}</b>
</button>
<section ng-show="aplhabet.newdiv">functionInvoked
</section>
</div>
in the ng-click pass the whole object as a parameter and change the newDiv to true
Demo
var myApp = angular.module('myApp',[]);
function MyCtrl($scope){
$scope.word = 'STRINGDEMO';
$scope.wordArr = [];
for(key in $scope.word){
$scope.wordArr.push({
letter : $scope.word[key],
newDiv : false
})
}
$scope.addDiv = function (aplhabet) {
aplhabet.newdiv = true;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<div ng-repeat="aplhabet in wordArr">
<button style="width:50px; margin: 5px" ng-click="addDiv(aplhabet)">
<b>{{aplhabet.letter}}</b>
</button>
<section ng-show="aplhabet.newdiv">functionInvoked
</section>
</div>
</div>
You need to pass $index
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<div ng-repeat="aplhabet in word track by $index">
<button style="width:50px; margin: 5px" ng-click="addDiv($index)">
<b>{{aplhabet}}</b>
</button>
<section ng-show="newdiv">functionInvoked</section>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope){
$scope.word = 'STRINGDEMO';
$scope.addDiv = function (index) {
//do your stuff here
}
}
</script>
</body>
</html>

How to instantiate ng-controller based on a condition

I asked this question but the specific question I'm asking has changed dramatically.
I have a piece of code:
<div ng-attr-controller="{{pings || 'PingsCtrl as pings' }}">
<h1 ng-click="pings.press()">asdf</h1>
</div>
This code is injected into two html pages. One page already calls PingsCtrl. The other doesn't. I'm really trying to keep this code DRY and I only want to have one reference of the code above.
How can I write the code above to generate ng-controller if PingsCtrl hasn't already instantiated.
Here are the two html pages.
HTML
// First page
<html ng-app="coolApp">
<div ng-controller="PingsCtrl as pings">
<div ng-attr-controller="{{pings || 'PingsCtrl as pings' }}">
<h1 ng-click="pings.press()">asdf</h1>
</div>
</div>
</html>
// Second page
<html ng-app="coolApp">
<div ng-attr-controller="{{pings || 'PingsCtrl as pings' }}">
<h1 ng-click="pings.press()">asdf</h1>
</div>
</html>
Javascript is here:
angular.module('coolApp', [])
.controller('PingsCtrl', function() {
var vm = this;
vm.press = function() {alert(123)};
})
What's wrong and how do I fix this?
Just use a service. It's really the intended structure for having common data and functionality between pages.
Part of the problem with what you were attempting is, whether or not you manage to preserve the controller, Angular has its own management that won't follow you with that, and will be refreshing components without you. You'll run into things like a $scope that doesn't actually match the page you're looking at, and it ends up causing more problems than it's worth.
I do have a solution but I also echo other people's concerns about the approach. You may want to have a global controller that you drop on the body for things that can happen anywhere and in most of the other controllers and just call through that. Eg
<body ng-controller="GlobalCtrl as gc">
<h1 ng-click="gc.pingPress()"></h1>
</body>
Anyway here is what I came up with.
<div ng-if="pings">
<h1 ng-click="pings.press()">asdf</h1>
</div>
<div ng-if="!pings">
<div ng-controller="PingsCtrl as pings">
<h1 ng-click="pings.press()">asdf</h1>
</div>
</div>
This will work if it is dropped inside or outside of an existing PingsCtrl.
Here is a plunker.
https://plnkr.co/edit/4x0kSazcg0g0BsqPKN9C?p=preview
Please, check my solution to see how to share data between controllers
var app = angular.module('myApp', []);
app.controller("aCtrl", function ($scope, PingList) {
$scope.addPing = function() {
PingList.add('Ping A');
};
});
app.controller("bCtrl", function ($scope, PingList) {
$scope.addPing = function() {
PingList.add('Ping B');
};
});
app.factory('PingList', function () {
var pings = ['Ping1', 'Ping2'];
return {
add: function(ping) {
pings.push(ping);
},
get: function () {
return pings;
}
};
});
app.directive('pingList', function(PingList) {
return {
restrict: 'EA',
link: function($scope) {
$scope.pings = PingList.get();
$scope.press = function(ping) {
alert(ping);
}
},
template: '<ul><li ng-repeat="ping in pings" ng-click="press(ping)">{{ping}}</li></ul>'
};
});
a, li {
cursor: pointer;
}
a {
color: blue;
text-decoration: underline;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="aCtrl" style="float: left">
<a ng-click="addPing()">click to add A ping</a>
<ping-list></ping-list>
</div>
<div ng-controller="bCtrl" style="float: right">
<a ng-click="addPing()">click to add B ping</a>
<ping-list></ping-list>
</div>
<div style="clear: both"></div>
</div>

How can I create custom scrollbar (jScrollPane) with knockout binding?

I have an observableArray for insert message. I want to display all messages using knockout. I want to use a custom scrollbar graphic - jScrollPane. (plugin LINK)
How can I fix my example for display custom scrollbar from jScrollPane?
Example: http://jsfiddle.net/aZpgY/3/
HTML:
<h2>Chat with knockout binding</h2>
<div id="chat-content" data-bind="foreach: messages">
<div data-bind="text: messageText"></div>
</div>
<br />
<form data-bind="submit: sendMessage">
message: <input data-bind="value: message" placeholder="Insert your massage" /><br />
<button type="submit">Add</button>
</form>
<h2>Example content - without knockout binding</h2>
<div id="example-content">
<div>Text</div>
<div>Text</div>
<div>Text</div>
<div>Text</div>
<div>Text</div>
<div>Text</div>
<div>Text</div>
<div>Text</div>
</div>
CSS:
#chat-content, #example-content{
width: 300px;
height: 100px;
overflow: auto;
}
JS:
function Message(data) {
this.messageText = ko.observable(data.messageText);
}
function ChatViewModel() {
// Data
var self = this;
self.messages = ko.observableArray([]);
self.message = ko.observable();
// Operations
self.sendMessage = function() {
self.messages.push(new Message({ messageText: this.message() }));
self.message("");
};
self.removeTask = function(task) { self.tasks.remove(task) };
}
ko.applyBindings(new ChatViewModel());
$(function(){
$('#chat-content').jScrollPane();
$('#example-content').jScrollPane();
});
Create a custom binding handler to initialize and update the jScrollPane instance:
ko.bindingHandlers.jScrollPane = {
init: function(element, valueAccessor) {
var o = valueAccessor() || {};
// initialize
$(element).jScrollPane(o.options);
var reinit = function() {
var scroll = $(element).data("jsp");
if (scroll) {
scroll.reinitialise();
}
};
// handle window resize (though not really necessary if your chat box has a set pixel width)
$(window).resize(reinit);
// add subscription to observable if passed in
if(o.subscribe) {
o.subscribe.subscribe(function() {
// use setTimeout so the DOM finishes updating before reinitialising
setTimeout(reinit, 0);
});
}
}
};
Call the new jScrollPane handler on the #chat-content div, passing in the messages observable so it knows when to refresh the scroll bar. Put the foreach binding handler in a child element to prevent it from interfering with the DOM changes that jScrollPane makes.
<div id="chat-content" data-bind="jScrollPane: { subscribe: messages }">
<div data-bind="foreach: messages">
<div data-bind="text: messageText"></div>
</div>
</div>
JSFiddle: http://jsfiddle.net/aZpgY/7/
jScrollPane binding handler adapted from here: http://jsfiddle.net/rniemeyer/NgNvf/

Categories

Resources