Change CSS of element not in DOM - javascript

I have an element, let's say a circle.
And there is a list of item in a list view.
Initially, the circle is not rendered. I have set its CSS display property to none, and instead select an item message is displayed. The idea is when the user selects an item from the list, I want to show the circle changing its background-color property associated with selected item.
My approach is something like this
itemSelected(item) {
const itemtColor = item.color;
$('#selected-item-color').css('background-color', itemColor);
}
The issue is:
As the element is not present in the DOM when I try $('#selected-item-color'), it will return an empty list, and the change of property does not do anything. But on next subsequent selections, it works fine.
So, is there any clean way to do this before the element is actually rendered in the DOM either jQuery or JavaScript. Or, should I just look for a way to do this after the element is rendered, which I'm not sure if there is a way with my problem.
Any help is appreciated, thanks!
Edit:
I have the HTML code written in handlebars
The code calling above function
<div class="item-list-content">
{{#each itemList as |item|}}
<div class="list-item" {{action "itemSelected" item}}>{{item.name}}</div>
{{/each}}
</div>
The dynamic content
<div class="item-details">
{{#if selecteditem}}
<div id="selected-item-color"></div>
{{else}}
<div class="item-details-message">Please select an item</div>
{{/if}}
</div>

To solve your problem you can just use ngShow instead of ngIf. There are better ways to do that as well, but for your issue ngShow will solve your problem.

I tried out few things and could not get anything to solve this.
So, I ended up doing the next best thing:
Putting the code to change project color inside a timeout function.
A timeout of 1ms worked for me, however I have not tested it well, and to be on a safer side, an interval of 10ms should work without problems.
setTimeout()

Related

Best Way To Reload Element Oriented JS On Element Replace

What's the best way to re-initialize javascript without a page refresh
I'm currently trying to append an MDBootstrap <select> tag, which is not as simple as adding a child element. Instead I'm removing the element and reconstructing it with the updated data via AJAX request.
At the moment, the only possibility I see is just executing the code again after the element is recreated.
Apologies if this isn't clear enough.
What I'm attempting to try, which works, however it's not very clean:
$("#function-btn").click(function(){
$.get("api/endpoint/getprofiles", function(){}).done(function(data){
$(".select-wrapper.mdb-select.md-form").remove()
$("#charcontainer").html(data);
$('.mdb-select').materialSelect();
})
// Reinitialize other JQuery functions around the '.mdb-select' element (alot)
})
Consider the following html
<div id='wrapper'>
<div id='container'>
<span>Content</span>
</div>
</div>
If you're deleting and replacing #container you will not want to hook your selector on #container but rather your jQuery should hook onto the parent (#wrapper) first and then drill down.
Therefore it will look something like this.
$('#wrapper>#container').on('click',function(){
//do the thing
});
That way you're technically not hooking onto the element that's removed from the DOM but rather the parent (#wrapper) element even though the selector has the child.

Why does knockout template binding stop working after manually reordering - and reverting - dom items?

I am using a knockout foreach (more specifically, template: { foreach: items }) binding to display a list of elements.
I then proceed to take the following actions:
Swap the first and second elements of the observable array. I see the changes reflected on screen, as expected.
Repeat the previous action to revert to the initial state. Again, this works as expected.
Now, swap the first and second DOM elements. I see the changes reflected on screen, as expected.
Repeat the previous action to revert to the initial state. Again, this works as expected.
Even though we have manually tampered with the DOM, we have reverted to exactly the initial state, without invoking knockout during the DOM tampering. This means the state is restored to the last time knockout was aware of it, so it should look to knockout as if nothing ever changed to begin with.
However, if I perform the first action again, that is, swap the first two elements in the array, the changes are not reflected on screen.
Here is a jsfiddle to illustrate the problem: https://jsfiddle.net/k7u5wep9/.
I know that manually tampering with the DOM managed by knockout is a bad idea and that it can lead to undefined behaviour. This is unfortunately unavoidable in my situation due to third party code. What stumps me is that, even after reverting the manual edits to the exact initial state, knockout still does not work as expected.
My question is: what causes this behaviour?
And then, how does one work around it?
Turns out there is nothing magical happening here. The mistake I made was to only consider elements instead of all nodes. The knockout template binding keeps a record of all nodes when reordering, not just elements.
Before manually editing the DOM, the child nodes of the template binding are:
NodeList(6) [text, div, text, text, div, text].
After manually swapping the first two elements using parent.insertBefore(parent.children[1], parent.children[0]), this turns into:
NodeList(6) [text, div, div, text, text, text].
Repeating the action yields:
NodeList(6) [text, div, div, text, text, text].
Although this is identical to the initial state when only referring to elements, it is quite different when referring to all nodes.
The solution now becomes clear. One way to perform a proper manual swap is to replace
parent.insertBefore(parent.children[1], parent.children[0]);
with
let nexts = [parent.children[0].nextSibling, parent.children[1].nextSibling];
parent.insertBefore(parent.children[1], nexts[0]);
parent.insertBefore(parent.children[0], nexts[1]);
as seen in https://jsfiddle.net/k7u5wep9/2/.
Obviously more care has to be taken when there are no text nodes before/after, but the idea remains the same.
By manipulating the DOM, you have broken the binding made.
Do not manipulate directly the DOM. Knockout will not detect the changes made.
If you put a with: items around your foreach, it at least keeps working but requires double click if dom order != array order .. might get you on track atleast, maybe you can re-order the ko-array inside the dom function to keep their 'orders' in sync?
let vm = {
items: ko.observableArray(['item1', 'item2']),
reorder_array() {
vm.items([vm.items()[1], vm.items()[0]]);
},
reorder_dom() {
let parent = document.querySelector('#items');
parent.insertBefore(parent.children[1], parent.children[0]);
vm.reorder_array();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: items">
<div id="items" data-bind="template: { foreach: $data }">
<div data-bind="text: $data"></div>
</div>
</div>
<button data-bind="click: reorder_array">Reorder array</button>
<button data-bind="click: reorder_dom">Reorder DOM</button>
<div>
Reorder the array twice, then reorder DOM twice. This should work as expected, and end up with the initial state. Then, try to reorder the array again. It should not work. Why?
</div>

Only render angular directive after a property has been set?

I have a loading view that's set as the html inside the directive. Something like the following.
<my-directive>
<div class="loader"></div>
</my-directive>
I'd like the HTML inside there to be active until some property of my directive has finished processing.
The closest thing I've found is transclude but I'm not sure that's really what I want.
Is there a way to avoid rendering until some arbitrary point in time?
you can hide a html element until a property return true
<div ng-if="taskFinished"></div>
or
<div ng-show="taskFinished"></div>
both do the same think but with ng-show the hidden content are not recalculated on each unhide
What Alainlb explained is also how I would approach the issue, show in the example below:
<my-directive>
<div ng-if="loading">
<div class="loader"></div>
</div>
<div ng-if="!loading">
<span>Do everything in here...</span>
</div>
</my-directive>
ng-if and ng-show/ng-hide
I personally will always use ng-if unless there is a very good reason not to. Since ng-if will only render the HTML when the check has passed. ng-hide on the other hand will make the HTML code invisible but it is still there! When the directive is very complex, then the browser is doing pointless work calculating and rendering things the user can't even see. To me, the only time it makes sense is when you want to collapse items that you know for sure the user will open very often. Even in this case I would still consider ng-if, unless it takes a long time to render each time.

Angularjs: Can't add class to ID

Got a little situation here.
I'm just learning Angular and I'm stuck with the following problem.
I've a list that repeats itself (ng-repeat) and when it is rendered, you can click on it and I add a class to the list-item you clicked on, jQuery-style:
$('#'+id).addClass("myClass");
And the id is stored in a scope-variable.
But when I change the scope for the list-item, new items are loaded, and when I change them back to the state were I started, I'm trying to add the class to the same id, but doens't work.
Do I miss something?
Thanks in advance
A better approach here would be to alter your data on click and then to conditionally attach a class based on the data:
<div ng-repeat="item in items" ng-class="{myClass:item.changed}">
{{item.name}}
<button ng-click="item.changed = !item.changed">Change me!</button>
</div>
Here it is in action: http://jsfiddle.net/wilsonjonash/NTpLS/
As I mentioned in the comments, when programming in angular, think in terms of your model first. Most things you would achieve in jQuery-land with DOM manipulations can be achieved in angular with model-dependent markup (directives).
Best of luck!
You can use the ng-class instead of addClass : )
ngClass Document

Find specific child of parent's previous siblings when siblings are dynamic

I ran into an issue trying to find the closest sibling of a parent that contained a specific child (one tree level). I finally found the answer through trial and error, but I searched on here before proceeding with testing. Thought I'd share my findings and see if anyone has an easier way to do what I'm doing.
Essentially I'm using jQuery 1.7 (latest as of 03/30/2012) and needed to find a title previously displayed for a "continue" heading in the following row. Here's what I came up with:
(($(this).parent().prevAll()).children('h2')).first().html();
I simply assigned this to a variable and used it to create a new "continue" title on the next row.
Any suggestions for cleaner code? Can this be done easier or more efficiently?
EDIT:
Here's the HTML as requested:
<div class="container">
<div class="subbox">
<h2>Title1</h2>
<div class="itembox">...some content here...</div>
</div>
<div class="subbox"><div class="itembox">...some content here...</div></div>
</div>
As you can see, it's simply a set of sub-boxes with the first one in the set having an H2, but in my case I was working with 4+ sets and needed to get the title of the first item in the set (not necessarily the title of the first set).
Not 100% sure if this will work since i haven't tested it, but you could simplify the code if you would have something like:
($(this).parent().prevAll()).children("h2:first").html();

Categories

Resources