I'm creating a grid of elements and have a hover effect in place, using CSS transitions, for each element. I'd like to add secondary effects on adjacent x- and y-axis elements as well, creating a cloud effect. I imagine I'll be referencing those elements using jQuery's next() and prev() methods, or by $index and $parent.$index.
The grid area will be large enough to prevent row-wrapping (using negative margins and hidden overflow).
Here's a simplified example of my repeat:
<div class="activity-thumb-row" ng-repeat="i in getNumArray(20) track by $index">
<div class="activity-thumb"
ng-class="{'adjacent': adjacent}"
ng-repeat="j in getNumArray(30) track by $index"
ng-mouseenter="highlightActivities()">
</div>
</div>
And a function in the controller (which I realize may not be the best approach):
$scope.highlightActivities = function() {
$(this).next().adjacent = true;
$(this).prev().adjacent = true;
}
How can I target elements adjacent to the hovered element using ng-class (or something else) inside ng-repeat?
Here's a fiddle for fiddling.
For reference, here are some related discussions:
Change class on mouseover in directive
Angular js ng repeat with conditional ng class not applying css class
ng-mouseover and leave to toggle item using mouse in angularjs
Here's a directive that calculates all of the indices of adjacent cells and adds the adjacent class using jQuery only ... not ng-class.
Assumes that rows will wrap , would need adjusting for individual row elements
.directive('activityThumb', function() {
return {
restrict: 'C',
link: function(scope, elem) {
elem.bind('mouseenter', function(e) {
var elW = elem.width(),
$parent =elem.parent(),
parentW = $parent.width(),
$items = $parent.children(),
numItems =$items.length
itemsPerRow = Math.floor(parentW / elW),
idx = elem.index(),
rowIndex = idx % itemsPerRow;
/* object of various indices , easy to inspect*/
var adjacentIdx = {
top: idx > itemsPerRow ? idx - itemsPerRow : false,
right: rowIndex != itemsPerRow ? idx + 1 : false,
left: rowIndex > 0 ? idx - 1 : false,
bottom: (numItems - idx) > itemsPerRow ? idx + itemsPerRow : false
}
console.dir(adjacentIdx);
$items.removeClass('adjacent')
$.each(adjacentIdx, function(position, index){
if(index !== false){
$items.eq(index).addClass('adjacent');
}
});
});
}
}
});
It wouldn't take much tweaking to remove jQuery dependency either.
Also would need additional directive on parent to remove extra classes when mouse leaves the main parent from one of the edges
DEMO
First, it's not a good idea to deal with DOM elements in the controller.
Also, this problem seems to be mostly styling related, and not functionality related. I would thus try to keep the logic in the View and not in the controller.
There are 2 ways to deal with View-specific logic: 1) using custom directives or 2) View-defined scope variables
The second approach can work here and seems like the cheapest approach, but also a bit ugly. It ng-inits the rowHighlight array in the scope and sets which element is highlighted:
<div ng-repeat="i in getNumArray(20) track by $index" ng-init="rowHighlight = []">
<div class="activity-thumb"
ng-repeat="j in getNumArray(30) track by $index"
ng-class="{'adjacent': rowHighlight[$index-1] || rowHighlight[$index+1]}"
ng-mouseenter="rowHighlight[$index] = true"
ng-mouseleave="rowHighlight[$index] = false">
</div>
</div>
updated fiddle
Related
When I have an element in a flexbox parent element, is there a way to get the distance to the parent element left/top boundary?
I would like to use a CSS animation to distribute the objects from 0,0 to their actual positions like this
#keyframes distribute_elements {
from {
translateX(calc(distance_to_parent_left));
translateY(calc(distance_to_parent_top));
}
to {
/* nothing special */
}
}
CSS isn't an actual programming language. You can't gather data using CSS. Javascript and jQuery are your only two options for the task you're trying to accomplish. You could also take the two numbers and calculate it yourself...
Javascript:
var child = document.getElementById('insertChildElementID').offsetTop;
var parent = document.getElementById('insertParentElementID').offsetTop;
var distance = child - parent;`
JQuery:
var child = $('#insertChildElementID').position.top;
var parent = $('#insertParentElementID').position.top;
var distance = child - parent;
For a better jQyuery explanation go to this website.
For a better Javascript explanation go to this website.
I'm using ui-grid and the first column have to be pinned on the left. When the user hovers on one row, I want to highlight the entire row, which is the logical thing to do.
The problem is that ui-grid creates two distinct elements, one for the pinned column and the other on for the "regular" ones. So I don't know how to highlight the entire row at once, and solutions with CSS don't work.
.ui-grid-row:hover .ui-grid-cell {
background-color: red;
}
Plunker here: http://plnkr.co/edit/HPxrc68JNMqyp4G9xLFA?p=preview.
Do you know how to do that ? Ideally just with ui-grid settings and CSS.
Thanks!
I solved it!
I used a row template that grabs the row id, defines ng-mouseover and ng-mouseout functions that fills the background for all the cells in the row. For whatever reason I had to wrap the entire template in a div, simply adding something to the class of the template broke the entire table.
Content of the rowTemplate:
<div class="row-uid-{{row.uid}}">
<div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.uid"
ng-mouseover="grid.appScope.onRowHover(row.uid);"
ng-mouseout="grid.appScope.onRowOut(row.uid);"
ui-grid-one-bind-id-grid="rowRenderIndex + '-' + col.uid + '-cell'"
class="ui-grid-cell"
ng-class="{ 'ui-grid-row-header-cell': col.isRowHeader}"
role="{{col.isRowHeader ? 'rowheader' : 'gridcell'}}"
ui-grid-cell>
</div>
</div>
Added functions in the controller:
$scope.onRowHover = function (rowUid) {
_.each(angular.element('.row-uid-' + rowUid + ' .ui-grid-cell-contents'), function (row) {
angular.element(row).css('background-color', 'red');
});
};
$scope.onRowOut = function (rowUid) {
_.each(angular.element('.row-uid-' + rowUid + ' .ui-grid-cell-contents'), function (row) {
angular.element(row).css('background-color', 'white');
});
};
I am trying to display some aggregate value (like total) in the last row of an ng-grid. The style and css class of the last row needs to be different than the other cells in that column. How to acheive this?
The cellTemplate in a column definition applies to all cells in that column, but in my case I need to have a different style for the last row in that column. Can anyone please suggest me a solution.
Thanks
Sudipta
I was able to add a class to the last row through a plugin:
function ngGridAddClassToLastRow(className) {
var self = this;
self.grid = null;
self.scope = null;
self.init = function (scope, grid, services) {
self.domUtilityService = services.DomUtilityService;
self.grid = grid;
self.scope = scope;
var addClass = function () {
var lastRow = self.scope.renderedRows[self.scope.renderedRows.length - 1];
lastRow.elm[0].className = lastRow.elm[0].className + ' ' + className;
};
self.scope.$watch(grid.config.data, addClass);
};
}
And with this added to the gridOptions:
...
plugins: [new ngGridAddClassToLastRow('<some class name>'),
...
And of course add some css, e.g. in my case:
.lastRow {
border-bottom: 0px;
}
That worked for me. I cannot say for certain that is the way to go since, needless to say, i'm a noob with Angular and ngGrid. I've constructed the plugin from flexible height plugin.
You can set a special property "isLast" (or however you like to name it) of the item that should be displayed in the last row. This item can be accessed through row.entity.isLast.
... somewhere in your controller ....
$scope.getRowClass = function(row) {
return row.entity.isLast === true ? 'lastRow' : '';
}
... somewhere inside the gridOptions ...
rowTemplate: '<div ng-style="{ \'cursor\': row.cursor }" ng-repeat="col in renderedColumns" ng-class="[col.colIndex(), getRowClass(row)]" class="ngCell {{col.cellClass}}">....</div>'
Based on the .lastRow class you could define a custom style for the last grid row.
I have a page that might one of the following:
<span id='size'>33</span>
Or
<span id='size'>
<b>33</b>
<strike>32</strike>
</span>
I would like to grab the value '33' on both cases, is there a CSS selector I can use?
I tried to use the following, #size with no b sibling or b which is a #size sibling:
document.querySelector('#size:not(>b), #size>b').innerText
But I keep getting an error- "Error: SYNTAX_ERR: DOM Exception 12"
According to w3 Spec only Simple Selectors are supported, the thing is that "greater-than sign" (U+003E, >)" is considered as part of the Simple Selectors definition.
You can't do it with a regular CSS selector, but you can do it in a few lines of JS:
var element = document.querySelector('#size');
var b = element.querySelector('b');
var text = b ? b.innerText : element.childNodes[0].nodeValue;
console.log(text);
So really you want significant text (ie other than whitespace, because in your second example there's probably tabs and returns between the span start tag and the b) of #size, or, if that doesn't exist, the significant text of its first element:
// Is text just whitespace?
function isWhitespace(text){
return text.replace(/\s+/,'').length === 0;
}
// Get the immediate text (ie not that of children) of element
function getImmediateText(element){
var text = '';
// Text and elements are all DOM nodes. We can grab the lot of immediate descendants and cycle through them.
for(var i = 0, l = element.childNodes.length, node; i < l, node = element.childNodes[i]; ++i){
// nodeType 3 is text
if(node.nodeType === 3){
text += node.nodeValue;
}
}
return text;
}
function getFirstTextNode(element){
var text = getImmediateText(element);
// If the text is empty, and there are children, try to get the first child's text (recursively)
if(isWhitespace(text) && element.children.length){
return getFirstTextNode(element.children[0])
}
// ...But if we've got no children at all, then we'll just return whatever we have.
else {
return text;
}
}
The day we'll have CSS Level 4 selectors and the parent selector you'll be able to use a simple selector but for now you can't do it directly.
You could iterate to find the first text node but here's a hacky solution :
var text = document.getElementById('size').innerHTML.split(/<.*?>/)[0];
To be used only if you have some idea of the content of your #size element.
I have written some code to hide specific markers in our maps based on checkboxes outside of the map itself. However, these markers also have vector features too (really on separate layers) but I want to just hide the features rather than destroy them. I tried using display(false) but get errors. Is there a function for hiding vectors?
Solution
Change the style property for OpenLayers.Feature.Vector instances. Set the display attribute to none or the visibility attribute to hidden. Redraw the layer afterwards.
According to comments in OpenLayers code:
display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect.
Example Code
For a given OpenLayers layer variable called layer, you could hide all the features as follows:
var features = layer.features;
for( var i = 0; i < features.length; i++ ) {
features[i].style = { visibility: 'hidden' };
}
layer.redraw();
This iterates over all features in a layer, allowing full control of the specific features to hide.
I have wrestled with OpenLayers a few times trying to get my features within the same layer to display exactly as I want. #igorti's solution overrides all of the feature's style properties, so I don't recommend this approach unless you have no reason to re-display the feature later on (in which case the removeFeatures() method is probably a better way to do this anyways).
Hiding Vector Features
The way I do this is to manually set the feature's style display to none and then redraw the layer. If I need to display the feature again, set the display property to block. Pretty simple:
function hideFeatures() {
var features = layer.features;
for (var i = 0; i < features.length; i++) {
var feature = features[i];
if (!isVisible(feature)) {
feature.style.display = 'none';
}
}
layer.redraw();
}
Re-Displaying Vector Features
Re-displaying hidden features is a little bit trickier depending on your situation. Take a look at the OpenLayers documentation on styling for some possibilities. But in general, if I need to display the feature again, I set the feature's style attribute to null. This ensures that when the OpenLayers renderer performs the drawFeature function, your pre-configured styles from your layer's styleMap are redrawn:
// from OpenLayers drawFeature()
if (!style) {
style = this.styleMap.createSymbolizer(feature, renderIntent);
}
So your display function might look something like this:
function displayFeatures() {
var features = layer.features;
for (var i = 0; i < features.length; i++) {
var feature = features[i];
if (isVisible(feature)) {
feature.style = null; //redraw the feature
}
}
layer.redraw();
}
Other Approaches
There are a couple other approaches to doing this. You can set the feature's fillOpacity and strokeOpacity to 0, like so:
function displayFeatures() {
var features = layer.features;
for (var i = 0; i < features.length; i++) {
var feature = features[i];
if (isVisible(feature)) {
feature.style.fillOpacity = 1;
feature.style.strokeOpacity = 1;
}
else {
feature.style.fillOpacity = 0;
feature.style.strokeOpacity = 0;
}
}
layer.redraw();
}
The downside to this approach is that any active map controls will still be able to interact with the "hidden" feature, so if a user accidentally clicks or hovers over the feature these events will still fire.
You can also create a style within your layer's styleMap called hidden, with either of the two approaches above. Then to hide a feature, simply change the feature's renderIntent to hidden.
Finally, you can add subsets of your features to separate layers, and call the layer's setVisibility method to false. This is only a good option if you don't have a need to interact with all features concurrently, since only controls for the top layer of your map will be active. (There are ways to use controls for multiple layers, but there's a lot more juggling involved and I don't recommend it unless it is absolutely necessary)
You can set display:'none' in style property. So that features will not be display
To hide features
for( var i = 0; i < features.length; i++ ) {
features[i].style = { display: 'none' };
}
layer.redraw();
To display back the hidden features
for( var i = 0; i < features.length; i++ ) {
features[i].style = null;
}
layer.redraw();
To hide the one feature
var feature = vectorlayer.getFeatureByFid(fid);
feature.style = { display: 'none' };
vectorLayer.removeFeatures(feature);
vectorLayer.addFeatures(feature);
It was not clear from your question whether you've already tried it, but if you haven't you might try the setVisibility() method.
Reference: http://dev.openlayers.org/releases/OpenLayers-2.10/doc/apidocs/files/OpenLayers/Layer-js.html#OpenLayers.Layer.setVisibility
Here is what I finally done for this matter as I had the same need but didn't want to hide each feature individually or play with CSS style:
I'll assume that you have something like the following somewhere:
myVector = new OpenLayers.Layer.Vector(...
Then link this code to the button or whatever event you need:
if( myVector.getVisibility() && myVector.features.length > 0 ) {
myVector.setVisibility(false);
} else {
myVector.setVisibility(true);
}
getVisibility() / setVisibility() references are missing from vector part but are in layer documentation.