Issue with JSViews and Materialize Dropdown Button - materialize

I have an issue with using a linked template with JSViews and Materialize Dropdown Button.
The first time, the view is rendered, it works fine, however as soon as I observably update the status of the button, the ul tag which is linked to the button is deleted from the DOM.
I have created a test case on JSFiddle: here
<script id="test_template" type="text/x-jsrender">
{^{for tasks}}
<div class="dropdown change-state" style="display: block">
{^{if Status == 0}}
<a href="#" class="completed-state-box dropdown-button btn btn-flat red" data-constrainwidth="false" data-activates='review_status_dropdown-{{:Id}}'>NOT COMPLETE</a>
{{/if}}
{^{if Status == 1}}
<a href="#" class="completed-state-box dropdown-button btn btn-flat green" data-constrainwidth="false" data-activates='review_status_dropdown-{{:Id}}'>COMPLETE</a>
{{/if}}
<ul id="review_status_dropdown-{{:Id}}" class="dropdown-content">
<li><a class="state-change-button" href="#" data-value="0">NOT COMPLETE</a></li>
<li><a class="state-change-button" href="#" data-value="1">COMPLETE</a></li>
</ul>
</div>
{{/for}}
If you click on a button after running, it opens the drop down just fine. If you select an option that is not set (ie set the COMPLETED button to NOT COMPLETE), it updates the UI, but as the ul is removed from the DOM, clicking on it again does not reveal the drop down. Only the button that is updated is affected.
Can anyone see where I am going wrong?

It looks like Materialize clones the ul and inserts a copy after each activator. Initially there is only one activator (since the other {^{if}} expression is false so its content is empty) - and the cloned ul is placed next to it inside the {^{if}} block.
When you click to toggle the status, the contents of that {^{if}} get removed, and the other {^{if}} contents get rendered. But the second activator (<a> tag) has not been 'activated' and so does not have an associated <ul>.
If you are going to use Materialize alongside JsViews, then you have to deal with Materialize potentially doing DOM manipulation, which can break the JsViews data linking. Having two frameworks which both manipulate the same DOM can lead to collisions or conflicts.
In this case you can resolve the problem by using the visible binding, rather than {^{if}}:
<div class="dropdown change-state" style="display: block">
<a href="#" data-link="visible{:Status==0}" class="completed-state-box dropdown-button btn btn-flat red" data-constrainwidth="false" data-activates='review_status_dropdown-{{:Id}}'>NOT COMPLETE</a>
<a href="#" data-link="visible{:Status==1}" class="completed-state-box dropdown-button btn btn-flat green" data-constrainwidth="false" data-activates='review_status_dropdown-{{:Id}}'>COMPLETE</a>
<ul id="review_status_dropdown-{{:Id}}" class="dropdown-content">
<li><a class="state-change-button" href="#" data-value="0">NOT COMPLETE</a></li>
<li><a class="state-change-button" href="#" data-value="1">COMPLETE</a></li>
</ul>
</div>
Update:
In response to grayson's Materialize-related issues of 'inline-block' vs 'block',
here are a couple of alternatives:
You can data-link directly to the CSS display attribute, and specify the values you want to toggle:
<a data-link="css-display{:Status==1?'inline-block':'none'}" ...>
Or you can create a custom tag that does what you want:
$.views.tags("show", {
render: function(val) {
return val ? "inline-block" : "none"
},
attr: "css-display"
});
and then write:
<a data-link="{show Status==0}" ...>
You can even do:
$.views.tags("show", {
render: function(val, type) {
return val ? type||"inline-block" : "none"
},
attr: "css-display"
});
and write
<a data-link="{show Status==0}" ...>
or
<foo data-link="{show Status==0 'block'}" ...>

Boris' Answer is the correct answer to this question as you would obviously expect, however, I wanted to add a little something which may help anyone in the situation.
JSViews is very clever and using the visible binding, it appears to try and identify the type of element, so in the case of an anchor tag it uses display:inline, whereas for a div it uses display:block
In the case of materialize (and bootstrap), the btn class sets the display to inline-block, so using visible binding doesn't work as intended in all cases.
In my case, because this truly was a button rather than a link, I changed the anchor tag to a button tag and as this is a block element it worked just fine.
It is not an ideal solution, as in my case, I might have 30 buttons, with 5 options which means flooding the DOM with lots of extra hidden tags, but until I can come up with a better solution, using the {^{if..., it works.

Related

ASP.NET Core Web App project (Dropdown buttons, handle click action, show on card with header

I'm new in this Visual studio coding.
I'm trying to handle the click event in a dropdown button in order to shoe me some content in the "header and footer card" on the same view.
I have this so far
<button type="button" style="margin:5px" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" >
DATA ANALYTICS
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Separated link</a></li>
</ul>
My question is the follow.
So I click the button. Then I want to show some content on the card, but I don't know where should I put the content (in cshtml or cshtml.cs) and how to handle the event (click).
As far as my understanding about this code is, I need to declare a var (id) inside of the div class.
Well I'm kind of lost right now.
I'll appreciate any help you guys.
P.S. This is not a homework question or something like that. I just want to learn more about this coding style.
If you want to click Action and then change the content of div,you can call JavaScript function in href:
<a class="dropdown-item" href="javascript:MyFunction();">Action</a>
js:
<script>
var id;
function MyFunction() {
//you can change the content of div here,also you can change a js variable here
}
</script>
You will have to use javascript to do this on the same page. If you want to be redirected to another page you can use tag-helpers for that, but I think it is not what you want. I would do it like so - either I would put an Id on the anchor tag, put href="javascript:void(0)" and in the script use:
<script>
const element = document.getElementById('id');
element.addEventListener('click', onClick);
function onClick(ev) {
ev.preventDefault();
//do rendering logic.
}
</script>
Or in the html:
<a class="dropdown-item" href="javascript:void(0)" onclick="javascript:onClickRender();">Action</a>
And script
<script>
function onClickRender(ev) {
//Render Logic
}
</script>
For this type of dynamic rendering you need javascript.

Materialize: Cannot set property 'tabIndex' of null at Dropdown._makeDropdownFocusable

I am trying to test my vuejs component via jest that contains materialize select.
When performing a component test, I get the following error in materialize.js:
TypeError: Cannot set property 'tabIndex' of null at Dropdown._makeDropdownFocusable
How fix this error?
This problem can happen when the input field is not wrapped inside a div with the class input-field:
<div class="input-field">
<input type="text" class="autocomplete"></input>
</div>
Adding a div with the class "input-field might solve this problem.
use id selector instead class selector. for example call dropdown like this :
html :
<a class='dropdown-trigger' id="dropdowner" href='#' data-target='dropdown1'>Drop Me!</a>
<!-- Dropdown Structure -->
<ul id='dropdown1' class='dropdown-content'>
<li>one</li>
<li>two</li>
<li class="divider" tabindex="-1"></li>
<li>three</li>
<li><i class="material-icons">view_module</i>four</li>
<li><i class="material-icons">cloud</i>five</li>
</ul>
js:
$('#dropdowner').dropdown();
Can only be used once.
data-target="name_target" must not be repeated
Exam1.❌
<nav>
<div class="nav-wrapper">
<ul class="right hide-on-med-and-down">
<li><a class="dropdown-trigger" href="#!" data-target="name_target1">Dropdown<i class="material-icons right">arrow_drop_down</i></a></li>
<li><a class="dropdown-trigger" href="#!" data-target="name_target1">Dropdown<i class="material-icons right">arrow_drop_down</i></a></li>
</ul>
</div>
</nav>
<!-- Dropdown Structure -->
<ul id="name_target1" class="dropdown-content">
<li>one</li>
<li>two</li>
</ul>
Exam2.✔️
<nav> <div class="nav-wrapper">
Logo
<ul class="right hide-on-med-and-down">
<li><a class="dropdown-trigger" href="#!" data-target="name_target2">Dropdown<i enter code here class="material-icons right">arrow_drop_down</i></a></li>
</ul> </div> </nav> <ul id="name_target2" class="dropdown-content"> <li>one</li> <li>two</li> </ul>
When I ran into this issue I was trying to create the whole dropdown list dynamically in JS. The fix for me was creating the list and any default list elements in HTML:
<div id="select1" class=\"input-field col s12\">
<select>
<option value="" selected>Default</option>
</select>
<label>Test</label>
</div>
Then appending any dynamic values in JS:
contents.forEach(function(content) {
var buffer = "<option></option>";
var template = $(buffer);
$(template).text(content);
$("select1").find("select").append(template);
});
$("select").formSelect();
pre 1.0.0 you would use data-activates, if data-target is not specified you will get this error
My problem was, that jQuery object was not attached to the DOM yet, so inner materialise code could not init element due to inability to find element by ID:
// materializecss initing dropdown (in my case for input autocomplete), where `t` is the input element
i.id = M.getIdFromTrigger(t),
i.dropdownEl = document.getElementById(i.id),
i.$dropdownEl = h(i.dropdownEl),
M.getIdFromTrigger(t) returned some random ID (not the one I provided) and dropdownEl was inited with null, and later method _makeDropdownFocusable failed on using it `this.dropdownEl.tabIndex = 0
So my problem code looked like this:
let root = $('#root'); // root in the DOM already
let wrapper = $('<div>'); // wrapper is just created and NOT attached to the DOM yet
let input = $('<input>').appendTo(wrapper); // creating input and attaching to the wrapper, but still not in DOM
initAutocomplete(input) // M.Autocomplete.init logic here FAILS
root.append(wrapper) // too late, error above
So the quick fix is to append elements first and only than do M.Autocomplete.init
I just stumbled this issue too while using Materializecss for my Vue project. As mentioned by sajjad, using id selector instead of class works. However, this is problem for initializing multiple dropdown, since each dropdown must have unique ID.
The way I solve this is just by selecting all the elements with the '.dropdown-trigger' class, and initialize every each of those. It works for me.
$.each($('.dropdown-trigger'), function(index, value) {
$(value).dropdown();
});

change active tab on document ready

I would like to change the active pill/tab on document load. I know you can set the active pill like I have below but for other reasons I want to change it after document load. I have tried various bits of JS but nothing seems to work. Here's the HTML and JS (I have also tried replacing data-toggle="pill" with data-toggle="tab" below and still doesn't work).
<div>
<ul class="nav nav-pills pillstyle">
<li class="active tabstyle"><a data-toggle="pill" href="#apple">Apple</a></li>
<li class="tabstyle"><a data-toggle="pill" href="#banana">Banana</a></li>
<li class="tabstyle"><a data-toggle="pill" href="#pear">Pear</a></li>
<li class="tabstyle"><a data-toggle="pill" href="#orange" >Orange</a></li>
</ul>
</div> <!-- nav pills close -->
<div class="tab-content">
<div id="apple" class="tab-pane fade in active"> `
.... content of tabs.
JS
$(document).ready(function(){
$('#banana').tab('show');
});
or
$(document).ready(function(){
$('#banana').pill('show');
});
You just need to change your jQuery selector to address the a element instead of the tab-pane div.
$(document).ready(function(){
$('a[href="#banana"]').tab('show');
});
If you need, you can find more detailed description about bootstrap tabs in the official documentation.
#Stu Here you go.
HTML:
Assign an ID myTab to UL element.
<ul class="nav nav-pills pillstyle" id="myTab">
JS:
$(function () {
$('#myTab a[href="#banana"]').tab('show');
});
Also refer to Bootstrap documentation on selecting different elements on load here. It will give you better understanding.
http://getbootstrap.com/2.3.2/javascript.html#tabs
Working demo: https://jsfiddle.net/tf9k9j27/
Note: Just to answer your trial and error.
You can activate a tab or pill navigation without writing any JavaScript by simply specifying data-toggle="tab" or data-toggle="pill" on an element. Adding the nav and nav-tabs classes to the tab ul will apply the Bootstrap tab styling. (From bootstrap docs. read more to get better clarity)

Twitter bootstrap dropdown show/hide events how do they work

I am trying to understand how the show/hide logic of a dropdown in twitter bootstrap works.
I can declare a dropdown menu in a navbar purely with HTML, and yet it seems that Javascript is involved with showing/hiding the dropdown. Also the dropdown disappears if I focus out by clicking on an unrelated area on the page.
Question part 1: Where can I find the Javascript code that is involved with Dropdowns in bootstrap?
Question part 2: Is it somehow possible to tap into the event logic and execute some custom Javascript code when a certain dropdown receives a focus out event? For example so that I can hide another (unrelated) element on the page when the dropdown looses focus?
To be more precise:
Lets say I declare a bootstrap dropdown purely in HTML (this would be inside a ul.nav.navbar-nav):
<li role="presentation" class="dropdown">
<a id="mainmenu" class="dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">
Lectures <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>Action</li>
</ul>
</li>
Now somewhere else on the page I have the following HTML:
<div id="#background" style="display: none">
<img src="...">
</div>
When I click on the dropdown button bootstrap will show the dropdown, but want two additional things to happen:
The div#background should be shown
Assuming I click somewhere outside the dropdown (which will cause bootstrap to close it), I want the div#background to be hidden again.
I think it should somehow be possible to register my own callback function on some bootstrap event and show/hide my div from that callback, like so:
/* just to illustrate my idea */
hide = function(element) {
element.style.display = "none"
}
show = function(element) {
element.style.display = "block"
}
}
So how and where could I register the hide and show functions such that they get called when I open/close the dropdown and also when I click on an unrelated area on the screen, which will cause the dropdown to close.
If this is possible to achieve purely in HTML by attaching some data-* elements to my div#background then I would like to know about that too.
First:
Do you think about something like this?
$('.dropdown-toggle').dropdown();
Got it from w3schools.com
Second:
You can do call the dropdown link
$('#element').click( function(){
$('#dropdownElement').dropdown();
});
Is that what you are looking for?

How to allow nested accordions but manage top level as accordion (and not collapsable) with materializecss?

I'm using nested accordions with Materializecss. I want to be able to have nested accordions, but to let each level to only have 1 item of the accordion opened (as of data-collapsible='accordion').
I can't get it to work: if I set data-collapsible='accordion' I cannot open nested accordions, and if I set data-collapsible='collapsible', I can open any number of items per accordion.
Any workaround?
Thanks!
If you are managing the inner elements of the collapsibles dinamically, then you need to "initialize" them using a jquery method included in "materialize.js". This is written in the "materializecss" documentation here.
I'll provide a practical example.
Given the next HTML:
...
<ul class="collapsible" data-collapsible="accordion">
<li>
<div class="collapsible-header">
My nested collapsible
</div>
<div class="collapsible-body">
<ul class="nested collapsible" data-collapsible="accordion">
<!-- No data initially -->
</ul>
</div>
</li>
<li>
<div class="collapsible-header">Second</div>
<div class="collapsible-body">
<p>Normal data...</p>
</div>
</li>
</ul>
...
I suppose the problem comes because you are appending data into the ".nested" div, and it's not working as an accordion as expected.
You should then do something like:
// ... Your handler code ...
// ... Data appended into $('.nested')
$('.nested').collapsible({accordion: true});
// ...
The {accordion: true} option is not mandatory, as it will be treated as an accordion by default.
It should work in this case. Good luck.