google maps select infowindow DOM element from angularjs directive - javascript

I need to select the element by class 'gm-style-iw' to fix some styles in Infowindow. The selection is happening inside of the angularjs directive.
<div ui-view="full-map" id="full-map" class="mainMap col-xs-6"></div>
ui-view - loading a directive with IW content. map is initialized inside that directive.
on the directive's controller initialization i have to edit element with class 'gm-style-iw'.
var iwElem = $document[0].getElementsByClassName("gm-style-iw")
does return the correct element.
console.log(iwElem) result is:
[]
length: 1
0: div.gm-style-iw
__proto__: HTMLCollection
However i'm stuck after it.
it as an HTMLCollection, which is the array of HTML elements, as i understand. => i must be able to get this 0 element by iwElem[0], the strange thing is that iwElem[0] returns undefined.
Also tried with jquery selectors:
$('.gm-style-iw') => length:0
$('div.gm-style-iw') => length:0

If you are sure that your html is something like
<div ui-view="full-map" id="full-map" class="mainMap col-xs-6">
...some codes
<div class="gm-style-iw"></div>
... other codes
</div>
And if your are using JQuery, how about trying the following selector?
$('.mainMap .gm-style-iw')
which means getting the child node of class .gm-style-iw under the parent node of class .mainMap

The problem is partially solved.
$timeout(function() {
removeIwStandardStyles()
},300);
function removeIwStandardStyles () {
var iwOuter = $(iwOuterTemp);
while (iwOuterTemp.length < 1){
iwOuterTemp = document.getElementsByClassName("gm-style-iw");
}
var iwBackground = iwOuter.prev();
iwBackground.children(':nth-child(2)').css({'display' : 'none'});
iwBackground.children(':nth-child(4)').css({'display' : 'none'});
}
$document[0].getElementsByClassName("gm-style-iw") was catching a corrent elements, but seems like they was not applied to DOM yet, or some other load issues.
However, the solution is not complete (see a while) - i suppose i can do that cause that element is definitely going to load. $timeout itself isn't helping completely - sometimes it does wait till complete IW load, sometimes not.

Related

How to call function after all multiple nested ng-repeats have finished?

I have been reading many threads on using a directive or $last to call a function at the end of an ng-repeat, even two level nested ng-repeats, but nothing regarding the level of nested ng-repeats I have. I think the issue is related to autoloading a new page/view while the ng-repeat is still rendering elements...the new page loads but going back to the list the ng-repeat created causes weird things to happen.
Simple example:
<div ng-repeat="products in categories" ng-init="$last && listLoaded()"> // length = 12
<div ng-repeat="product in products">
<div ng-if="product.type == 1" ng-repeat="feature in product.features"> // length = 20
<div ng-if="feature.specs == 1" ng-repeat="specs in feature.specs"> // length = 40
</div>
<div ng-if="feature.desc == 1" ng-repeat="desc in feature.desc"> // length = 4
</div>
<div ng-if="feature.reviews > 0" ng-repeat="review in feature.reviews"> // length = 125
<div>{{review.review}}
<div ng-if="review.pictures > 0" ng-repeat="pictures in review.pictures"> // length = 3
<img src="blah.com/imgs/{{picture.picURL}}" id="picture.pictureID" ng-click="enlargeImage(picture.pictureID)">
</div>
</div>
</div>
</div>
</div>
$scope.listLoaded = function() {
if ($stateParams.prodID) {
//external ID passed in, autoload featurePage
$state.go("tabs/search/" + featurePage", {product : productID}) ;
}
}
$scope.enlargeImage = function(id) {
var el = document.getElementById(id) ;
el.style.border = "1px solid red" ;
el.style.display = "block" ;
console.log(el) ;
angular.element('#'+id).border = "1px solid red" ;
angular.element('#'+id.display = "block" ;
}
As you can see, there are multiple nested ng-repeats, all of varying lengths that will all have vastly different rendering times. I need to execute a function when ALL items, from all nested ng-repeats are done rendering, the function listLoaded calls to another tab/view/page.
When an external ID is passed in it autoloads a feature page. It all works, featurePage is loaded with all the correct info. However, when I go back to the list page, the full page is there, but when clicking on the images, though the function is executing, the images aren't getting the styles applied to them.
The console.log output of el shows the styles applied to the element, but in Developer Tools, in the Elements -> Styles section, the styles aren't actually being applied to the element and visually none of the image styles are changing.
console.log output:
<img src="blah.com/imgs/{{picture.picURL}}" id="picture.pictureID" ng-click="enlargeImage(picture.pictureID)" style="border:1px solid red;display:block;">
After an autoload to the featurePage, if I refesh the product list page, the same elements that weren't rendering style changes still have issues, they won't change. The view is cached. When I remove page caching - everything works properly. So, the issue must be related to the DOM elements not fully rendering or being added to the DOM.
When I comment out the listLoaded() function and don't force an autoload of the featurePage, the full list renders and all the ng-clicks work properly. I can then manually click on a specific product to go to the featurePage - and then back to the list again and everything still works. I think the issue has something to do with the angular.element not fully loading before the page view is auto-loaded to the featurePage - but I am not certain. All I know is everything works when I don't auto-redirect to the featurePage
I even tried a directive on the first ng-repeat and still have the same issues:
.directive('listRepeatDirective', function() {
return function(scope, element, attrs) {
angular.element(element).css('color','blue');
if (scope.$last){
scope.listLoaded() ;
}
};
})

When appending items dynamically, masonry does not layout them out

I am using a Masonry JavaScript grid layout library https://masonry.desandro.com/. I have an issue when appending new items to the grid directly from the DOM using Angular *ngfor which iterate through an a array as follows:
<div class="grid">
<div class="grid-item grid-item--height{{i}}" *ngFor="let i of array">
{{i}}
</div>
</div>
Because I am not appending them using the "appended masonry method", masonry does not layout them out. The array is getting new elements every time a user scroll down. So a JavaScript method is called:
onScrollDown() {
// add another 20 items
this.sum += 20;
for (let i = start; i < this.sum; ++i) {
this.array.push(i); }
}
When the array get more elements, it automatically creates new html items. So i need to find the way masonry layout them out again after the new items are being created. I tried calling $grid.masonry('layout') after the elements have being added to the array in the OnScrollMethod but it did not work.
I am trying $('.grid').masonry('reloadItems') as well, but calling this method the new items are overlapping the previous ones.
I would appreciate any help.
Update:
I am initializing masonry using my angular initialize component method as follows:
ngOnInit() {
this.$grid = jQuery('.grid').masonry({
// options
itemSelector: '.grid-item',
columnWidth: 384,
gutter: 24
});
UPDATED
You might try reloadItems. If your loading images, use imagesLoaded.js to avoid overlaps:
ngOnInit() {
this.$grid = jQuery('.grid').imagesLoaded( function() {
jQuery('.grid').masonry({
// options
itemSelector: '.grid-item',
columnWidth: 384,
gutter: 24
});
});
onScrollDown() {
// add another 20 items
this.sum += 20;
for (let i = start; i < this.sum; ++i) {
this.array.push(i); }
jQuery('.grid').imagesLoaded( function() {
jQuery('.grid').masonry('reloadItems');
});
}
It's been a lot of years since I had to do this, but basically:
Masonry runs a lot of JS, in order to figure out optimal bin-packing for the elements you are creating.
That means that it hard-codes a lot of style information on each "card" inside of your area, and after it calculates it, it sets it.
If you use Masonry to add new "cards" to that view, then it will continue to update the view, and change the layout and the sizes of those cards.
If you don't use the Masonry instance you had before (you use the element, not the Masonry object), then Masonry doesn't have a way to track the changes. At that point, you either have to add them to the DOM and then add them to the object, through addItems, and THEN call layout() (or masonry())... or you have to rewrap the DOM element in Masonry again, now that the element has updated.
This might have changed, but I doubt it.
There are things you could do by writing angular directives, I guess, but the cheap and cheerful way of accomplishing this would be to save the instance of your component's element (or the element that controller is bound to... whatever), and also save the reference to the Masonry view you made, and on update, add all of the items, and call layout again...
...or just, on each render, make a new Masonry view, using your root element.
PS: make sure to load a lot of new elements at once, because if you're just loading 3 at a time it'll be messy...
...also, make sure you pre-load the images in the elements, because if the images load afterwards your Masonry layout is going to get very, very messy.
I used masonry-layout in Vue.js and had the same problem. The way that solved my issue is:
onScrollDown() {
// ...
// after add another 20 items
this.msnry.reloadItems();
this.msnry.layout();
}
That this.msnry is my masonry-layout instance.

How can I change css for every DOM object in the page in SAPUI5

I have a things inspector and this things inspector has two titles. I wan to be able to change the css on this titles and make their font size a bit smaller( default fontSize is 16px and I want to drop it to 12px). I tried to get these titles class and use this method to change their size:
var element = document
.getElementsByClassName("sapUiUx3TVTitleSecond")[1];
element.style.fontSize = '12px';
var element = document
.getElementsByClassName("sapUiUx3TVTitleFirst")[1];
element.style.fontSize = '12px';
it does work and I can see the change but as soon as the page finishes loading ( page loading takes couple of second because it needs to read a json object) title sizes go back to its default.
I dont even know this is a right way to access DOM elements and change their CSS.
Please point me to the right direction of how to change DOM object css in SAPUI5 in general
You could create a new CSS file which you include in your index.html.
Just add the needed selectors with the modified attributes in this custom CSS:
.sapUiUx3TVTitleSecond, .sapUiUx3TVTitleFirst {
font-size : 12px;
}
Edit: if you need to change it programmatically, you could use the addStyleClass("yourStyle") method which is available to every UI element
Execute the script after dom gets completely loaded. Try like this
$("document").ready(function()
{
var element = document
.getElementsByClassName("sapUiUx3TVTitleSecond")[1];
element.style.fontSize = '12px';
var element = document
.getElementsByClassName("sapUiUx3TVTitleFirst")[1];
element.style.fontSize = '12px';
})
$("document").ready(function()
{
$(".sapUiUx3TVTitleSecond").css("font-size","12px");
})

Dojo destroy MenuItems on DropDownButton

I have the following markup:
<button dojoType="dijit.form.DropDownButton" dojoAttachPoint="labels" label="Labels">
<div dojoType="dijit.Menu" dojoAttachPoint="labelsMenu"></div>
</button>
I am adding MenuItems programatically and it works fine for the first time. But when I want to refresh I get an error: Tried to register widget with id==16 but that id is already registered. I have tried the following code to clear but it's not working:
var labels = dijit.findWidgets(this.labels);
dojo.forEach(labels, function (l) {
l.destroyRecursive();
});
dojo.empty(dojo.byId(this.labels));
I have also try the same thing for labelsMenu to empty it but no luck. Is there any other way to get rid of all children when reloading data or am missing something?
I have solved it and here's what I did:
var menuChildren = dijit.byId(this.labelsMenu).getChildren();
if (menuChildren.length > 0){
dojo.forEach(menuChildren, function(mc){
mc.destroyRecursive();
});
}
In your code you call dojo.empty on the labels. dojo.empty() empties the element in the DOM but keeps the original element. So try calling dojo.empty on the dijit menu instead.
dojo.empty(dojo.byId("labelsMenu"));
For reference, in a fully baseless application, the dom-construct module is used.
require(["dojo/dom-construct"], function(domConstruct){
// Empty node's children byId:
domConstruct.empty("someId");
});

Wrapping a jquery validate span.error with nested divs

Heyo. This is my first stack overflow post because I am stumped and not finding many people who are trying to accomplish the same thing. I've tried using jquery .before(), .after(), and .wrap() to resolve this. I was initially using css :before and :after pseudo-elements, but as that won't work for legacy browsers, I've decided to use jquery.
I already have several forms on several pages with validation working. The error messages vary in length. We were using a static, one size background image on the default span element, so content was bleeding out on longer error messages. I built a flexible rounded corner series of nested divs to allow the error box to grow or shrink dynamically. The html I want to output is:
<div class="errorWrap">
<div class="errorTop"><span></span></div>
<div class="errorContent">
<span class="error">This is an error</span>
</div>
<div class="errorBottom"><span></span></div>
</div>
Here's an example of a solution I tried, but I'm still pretty new to javascript.
$('.error').before('<div class="errorWrap"><div class="errorTop"><span></span></div><div class="errorContent">');
$('.error').after('</div><div class="errorBottom"><span></span></div></div>');
Correct me if I'm wrong, but I think that I have the right idea with the jquery. But it's just kind of sitting there, not in any function being called. So I imagine that since the code isn't re-executing, it just doesn't show up. Is there an appropriate function to wrap this in? I'm certain I'm just not attacking this from the right direction. Any help is super appreciated.
the plugins "before" and "after" dont take html as string. you cannot start a div in one and close it in an other.
Either you take your current html and generate a new html string which you append where you want to or you use the "wrap" plugin http://api.jquery.com/wrap/
Using pure HTML
$(".error").html("<div class='beforeContent'>" + $(".error").html() + "</div>");
Using wrap (http://api.jquery.com/wrap/)
$(".error").wrap("<div class='beforeAndAfter'></div>");
If you want to show an error div after focus out of an input then you have to create it using html/wrap as Luke said and then you have to append it in ot the dom useing
$('.errorWrap').insertAfter('.focusedElement');
But there are other methods available to insert a new element like append/appendTo e.t.c,
I ended up fixing this problem on my own using jquery to create the div and it's nesting on pageload, the divs are generated with an error class that gives display:none. A custom errorPlacement function nests the error in the correct div. Then I used a custom validator highlight function to remove the class that hides the element. Then I used the unhighlight function to re-add the class to re-hide the div.
$(function() {
//Generate the elements and assign attributes
var errorWrap = document.createElement('div');
$(errorWrap).addClass('errorWrap hideError');
var errorTop = document.createElement('div');
$(errorTop).addClass('errorTop');
var topSpan = document.createElement('span');
var errorContent = document.createElement('div');
$(errorContent).addClass('errorContent');
var errorBottom = document.createElement('div');
$(errorBottom).addClass('errorBottom');
var bottomSpan = document.createElement('span');
//Place the elements directly after each dd element
$("dl > dd").append(errorWrap);
$("div.errorWrap").append(errorTop)
.append(errorContent)
.append(errorBottom);
$("div.errorTop").append(topSpan);
$("div.errorBottom").append(bottomSpan);
//Add custom validator defaults
$.validator.setDefaults({
errorPlacement: function(error, element) {
$(element).nextAll('.errorWrap').children('.errorContent').append(error);
},
highlight: function(element) {
$(element).nextAll('.errorWrap').removeClass('hideError');
},
unhighlight: function(element) {
$(element).nextAll('.errorWrap').addClass('hideError');
}
});
}
Although I'm sure this could have been done more shorthand, I really like this technique because I didn't have to update any of my pages that contained forms to get it to work. All of the nested divs are dynamically created by javascript, so I can include a global file to any page with forms and it will just work. Thanks for all who offered suggestions.

Categories

Resources