How to Attach Events to Table Checkboxes in Material Design Lite - material-design-lite

When you create a MDL table, one of the options is to apply the class 'mdl-data-table--selectable'. When MDL renders the table an extra column is inserted to the left of your specified columns which contains checkboxes which allow you to select specific rows for actions. For my application, I need to be able to process some JavaScript when a person checks or unchecks a box. So far I have been unable to do this.
The problem is that you don't directly specify the checkbox controls, they are inserted when MDL upgrades the entire table. With other MDL components, for instance a button, I can put an onclick event on the button itself as I'm specifying it with an HTML button tag.
Attempts to put the onclick on the various container objects and spans created to render the checkboxes has been unsuccessful. The events I attach don't seem to fire. The closest I've come is attaching events to the TR and then iterating through the checkboxes to assess their state.
Here's the markup generated by MDL for a single checkbox cell:
<td>
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect mdl-data-table__select mdl-js-ripple-effect--ignore-events is-upgraded" data-upgraded=",MaterialCheckbox">
<input type="checkbox" class="mdl-checkbox__input">
<span class="mdl-checkbox__focus-helper"></span>
<span class="mdl-checkbox__box-outline">
<span class="mdl-checkbox__tick-outline"></span>
</span>
<span class="mdl-checkbox__ripple-container mdl-js-ripple-effect mdl-ripple--center">
<span class="mdl-ripple"></span>
</span>
</label>
</td>
None of this markup was specified by me, thus I can't simply add an onclick attribute to a tag.
If there an event chain I can hook into? I want to do it the way the coders intended.

It's not the nicest piece of code, but then again, MDL is not the nicest library out there. Actually, it's pretty ugly.
That aside, about my code now: the code will bind on a click event on document root that originated from an element with class mdl-checkbox.
The first problem: the event triggers twice. For that I used a piece of code from Underscore.js / David Walsh that will debounce the function call on click (if the function executes more than once in a 250ms interval, it will only be called once).
The second problem: the click events happens before the MDL updates the is-checked class of the select box, but we can asume the click changed the state of the checkbox since last time, so negating the hasClass on click is a pretty safe bet in determining the checked state in most cases.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
$(document).on("click", ".mdl-checkbox", debounce(function (e) {
var isChecked = !$(this).hasClass("is-checked");
console.log(isChecked);
}, 250, true));
Hope it helps ;)

We currently don't have a way directly to figure this out. We are looking into adding events with V1.1 which can be subscribed to at Issue 1210. Remember, just subscribe to the issue using the button on the right hand column. We don't need a bunch of +1's and other unproductive comments flying around.
One way to hack it is to bind an event to the table itself listening to any "change" events. Then you can go up the chain from the event's target to get the table row and then grab the data you need from there.

You could delegate the change event from the containing form.
For example
var form = document.querySelector('form');
form.addEventListener('change', function(e) {
if (!e.target.tagName === 'input' ||
e.target.getAttribute('type') !== 'checkbox') {
return;
}
console.log("checked?" + e.target.checked);
});

Related

Ready event for templates

In JsViews i can bind events in the following way:
<li id="myElement" data-link="{on 'click' eventHandler}">Some Content</li>
This will execute the method "eventHandler" after a click.
But I need an event which will be fired when the template is loaded. I tried "ready" or "show", but nothings works. Is there a event which can handle this?
The {on xxx eventHandler} handles events on HTML elements, such as mouse events, submit, change, blur, etc.
With JsViews, the loading of the template happens directly as a result of your own code calling the link method. So elements in the rendered template will have been rendered during that call, and immediately after you can run whatever code you want to call after rendering and linking, such as using jQuery to find your <li> element, and act on the element
JsViews also provides many life-cycle events on tags, so if you want you can create a custom tag just for handling those events:
For example, try running the following code:
<span id="result"></span>
<script>
var data = {};
$.views.tags("test", {
attr:"none",
render: function(data) {
debugger;
},
onBind: function(tagCtx, linkCtx) {
var elem = this.parentElem;
elem.textContent += " added text";
}
});
var myTmpl = $.templates('<ul><li id="myElement" data-link="{test}">Some Content</li></ul>');
myTmpl.link("#result", data);
$("#myElement").css('color', 'red');
</script>
You could use an onload event:-
https://www.w3schools.com/jsref/event_onload.asp
and attach that to the template itself. If you're limited in your options or need to do it in a specific way, explain the use case and why you want to do it a certain way and we'll try to help.
All the best,
Phil

How do I use Dynamic Fields in aslagle/reactive-table

Does anyone have more details on how to use Dynamic Fields in aslagle/reactive-table (I find the documentation confusing.
I'm trying to add a column with a check box, such that as a person clicks on a row, the check box is toggled.
This way a user can page through a table of records and select items, then when they are done selecting I'll save their final choices.
Right now I can only capture click events by row. But I cannot figure out how to save clicks from as a person moves from one page to another.
Here's how I create the checkbox, using a columncell template; I'm using a unique record id (i.e. 'rin') as a html element id.
<template name="checkboxCellData">
<input type="checkbox" id="{{ rin }}" checked="{{ clicked }}">
</template>
Heres the event toggle.
Template.regsUnderReview.events({
'click #reactive-table-1 tbody tr': function (event) {
// When the row is clicked get that rows data
var row = this;
let cb = document.getElementById(row.rin);
if (row.clicked) {
row.clicked=false;
//set check box when row is clikked
cb.checked = false;
} else {
row.clicked = true;
cb.checked=true;
}
}
});
I think I'm only saving the checkbook state in the DOM, and not the correct Reactive table location....I don't want to store the value in the database, because I'll have tons of users saving their selections...I'll only want cache their selections in the web browser and then on final seletion, save the IDs selected to a user settings database.
Meteor sessions may be what you're looking for.
You can initiate one with Session.set("yourKey", "yourValue"), and you can get the data by using Session.get("yourKey"). Sessions in Meteor are also reactive.
If you are using Meteor 1.3+, you will probably have to add the session package with meteor add session in order to use the above methods.

Add selectedItemCls to First Item of DataView ExtJS

I have ExtJS Dataview with following template for its Items.
<div class="item-container">
<div class="{itemCls}"></div>
<span class="item-title">{title}</span>
</div>
Now, I have set selectedItemCls config with value item-selected, which is styled accordingly in my stylesheet. When I click on items in dataview, it works fine, and selected item is highlighted. But I want to have first item of my dataview be selected by default (and thus having item-selected class already added to it).
I tried to play around the dataview component in its afterrender event but I couldn't add active class to first item.
Update
Note that I want to maintain that toggle behavior of class, so when dataview is rendered and user clicks on other item, first item (on which tab-selected was added programmatically) is no longer highlighted and clicked item gets tab-selected class.
So far, I tried calling dataview.selModel.select(dataview.Store.getAt(0)) within afterrender event, and surprisingly, it works while debugging (breaking execution in afterrender event and proceeding step-by-step) but it throws Uncaught TypeError: Cannot call method 'addCls' of null if I try it without DevTools open. I believe it is due to event bubbling since select() also fires itemclick and selectionchange events internally (I might be wrong though).
P.S. Using Chrome for debugging.
How to attain this?
Try this:
new Ext.XTemplate(
'<tpl for=".">',
'<div class="item-container {[xindex === 1 ? "item-selected" : ""]}">',
'<div class="{itemCls}"></div>',
'<span class="item-title">{title}</span>',
'</div>'
'</tpl>,
)
You could also try this:
listeners: {
afterrender: function(dv) {
dv.selModel.select(dv.store.getAt(0));
}
}
After hacking around for more than an hour, I found the fix!
When using afterrender event, where DataView is technically not fully rendered on UI, calling select() will fire itemclick event but there's nothing on UI, so it would lead to the TypeError I was getting (I'm still not 100% sure if this is the exact reason for TypeError I got).
So I have two possible fixes for this, where I'd prefer 2nd fix.
Option - 1
Make called to select() deferred by a few Milliseconds.
listeners: {
afterrender: function(dv) {
Ext.defer(function() {
dv.selModel.select(dv.store.getAt(0));
}, 10); // Some small number for delay will do.
}
}
Option - 2 (Recommended)
Use viewready event instead of afterrender.
listeners: {
viewready: function(dv) {
dv.selModel.select(dv.store.getAt(0));
}
}
I've used 2nd option and it works perfectly, however, any better suggestion is welcome.

What is the 'angular' way of displaying a tooltip / lightbox?

I've been looking around and have not been quite able to get a clear path to the 'angular' way of accomplishing the following. What I'm trying to achieve is displaying a tooltip with information when hovering over a link within an ng-repeat loop. Based on my research, I understood that this is part of the view, and so I should probably handle this in a directive. So, I created an attribute directive called providertooltip. The html declaration is below:
<table>
<tr id="r1" ng-repeat="doc in providers">
<td>
<a providertooltip href="#{{doc.Id}}" ng-mouseover="mouseOverDoc(doc)" ng-mouseleave="mouseLeave()">{{doc.FirstName}} {{doc.LastName}}</a>
</td>
</tr>
</table
<div id="docViewer" style="display:hidden">
<span>{{currentDoc.FirstName}} {{currentDoc.LastName}}</span>
</div>
In the module, I declare my directive, and declare my mouseOver and mouseLeave functions in the directive scope. I also 'emit' an event since this anchor is a child scope of the controller scope for the page. On the controller function (docTable ) which is passed as a controller to a router, I listen for the event. Partial implementation is seen below:
app.directive("providertooltip", function() {
return {
restrict : 'A',
link: function link(scope, element, attrs) {
//hover handler
scope.mouseOverDoc = function(doc){
scope.currentDoc = doc;
scope.$emit('onCurrentDocChange');
element.attr('title',angular.element('#docViewer').html());
element.tooltipster('show');
//docViewer
};
scope.mouseLeave = function() {
element.tooltipster('hide');
}
}
}});
function docTable(docFactory, $scope, $filter, $routeParams) {
$scope.$on('onCurrentDocChange',function(event){
$scope.currentDoc = event.targetScope.currentDoc;
event.stopPropagation();
});
}
Ok, so here is my question. All of the works as expected; Actually, the tooltip doesn't really work so if someone knows a good tooltip library that easily displays div data, please let me know. But, what I'm really confused about is the binding. I have been able to get the tooltip above to work by setting the title ( default tooltip behavior ), but I can see that the binding has not yet occured the first time I hover of a link. I assume that the onCurrentDocChange is not synchronous, so the binding occurs after the tooltip is displayed. If I hover over another link, I see the previous info because as I mentioned the binding occurs in an asynchronous fashion, i.e., calling scope.$emit('onCurrentDocChange') doesn't mean the the parent scope binds by the time the next line is called which shows the tooltip. I have to imagine that this pattern has to occur often out there. One scope does something which should trigger binding on some other part of the page, not necessarily in the same scope. Can someone validate first that the way I'm sending the data from one scope to the other is a valid? Moreover, how do we wait until something is 'bound' before affecting the view. This would be easier if I let the controller mingle with the view, but that is not correct. So, I need the controller to bind data to the scope, then I need the view to 'display a tooltip' for an element with the data. Comments?
To go the angular way correctly start your directive like:
...
directive('showonhover',function() {
return {
link : function(scope, element, attrs) {
element.parent().bind('mouseenter', function() {
element.show();
});
element.parent().bind('mouseleave', function() {
element.hide();
});
}
...
Or start with http://angular-ui.github.io/ link to go the angular-way UI. Look into the bootstrap-ui module - pure angular bootstrap widgets implemented as directives. You can get a clue how the tooltip binding implemented directly from the source of the module - https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js
Also here is another example - (having jQuery and bootstrap scripts included) - use the ui-utils module Jquery passthrough directive ui-jq'. It allows to bind Jquery plugins ( style of $.fn ) directly as angular directive.
Here is their example for binding twitter bootstrap tooltip.
<a title="Easiest. Binding. Ever!" ui-jq="tooltip">
Hover over me for static Tooltip</a>
<a data-original-title="{{tooltip}}" ui-jq="tooltip">Fill the input for a dynamic Tooltip:</a>
<input type="text" ng-model="tooltip" placeholder="Tooltip Content">
<script>
myModule.value('uiJqConfig', {
// The Tooltip namespace
tooltip: {
// Tooltip options. This object will be used as the defaults
placement: 'right'
}
});
</script>
Also look into the official angular documentation for writing directives examples,
and have a happy coding time with Angular!

jQuery.click(): Can I get a reference to the synthetic event object passed to event handlers?

I have an <a> inside a <TD>, and I'm trying to have clicks on the <TD>, but outside the <A>, act like they were clicks on the <a>. I'm almost there:
HTML:
<TD class="somethingPretty">
Text
</td>
JS:
$('.anchor').click(function(ev){return confirm("go ahead?");});
$('somethingPretty').click(function(ev){
if($('.anchor').click()){
document.location = $('.anchor').attr('href');
}
}
The problem with this is that jQuery.click returns undefined, and I can't see how to get at the event object that's passed to the click handlers so I can interrogate it with isPropagationStopped and isDefaultPrevented. What's the right way to solve this problem?
Sometimes asking the question clearly is the best way to find an answer. Some strategic poking around the jQuery source led me to the following solution(using the markup above):
$('.somethingPretty').click(function(ev){
var syntheticClick = new $.Event("click");
syntheticClick.stopPropagation();
$('.anchor').trigger(syntheticClick);
if(syntheticClick.isDefaultPrevented()) return;
document.location = $('.anchor').attr('href');
}
This works for all event handlers except live-bound ones (those don't execute; my users will have to learn to click the anchor itself for them!). The tricky part here is that trigger takes a jQuery.Event in addition to the documented string.
How about this?
var a = $('.somethingPretty .anchor');
var td = $('.somethingPretty');
a.click( function(ev) { return confirm("go ahead?"); } );
td.click( function() { a.click(); } );
Did you try something like:
$("td.outer").add("td.outer a").click(function() {
// do stuff
});
You're going to want to find some way to ensure that whatever is in the function runs only once, since a click on the <a> will count both as a click on the <td> and the <a>.

Resources