I am currently using angular to build an application that uses buttons within Datatables to manipulate data. The current buttons I have available are "View" "Mark as completed", "Mark as incomplete", which upon clicking trigger specific functions. The relevant functionality is being achieved by the following:
/**
* Hides ID and adds onclick functionality
*/
ngAfterViewInit() {
const table = $('.display').DataTable({
responsive: true,
bRetrieve: true
});
table.column(0).visible(false);
this.clickListener(table, this.route);
}
/**
* Listens to row click
*/
clickListener(table: any, route: any) {
const self = this;
let rowData;
$('.display').on('click', 'tbody tr td .view', function(e) {
rowData = self.checkIfRowsHidden(table, this, e);
self.router.navigate([route + '/detail/' + rowData[0]]);
});
$('.display').on('click', 'tbody tr td .done', function(e) {
rowData = self.checkIfRowsHidden(table, this, e);
self.executePutData(rowData[0], 'Completed', self);
});
$('.display').on('click', 'tbody tr td .undone', function(e) {
rowData = self.checkIfRowsHidden(table, this, e);
self.executePutData(rowData[0], 'In Progress', self);
});
}
As you can see I am using var self = this in order to create a reference to the global scope. Please also note I am also using this within the function checkIfRowsHidden to capture the local scope. The function uses the local scope like this:
checkIfRowsHidden(table, scope, event) {
event.stopImmediatePropagation();
if (table.responsive.hasHidden()) {
const parentRow = $(scope).closest("tr").prev()[0];
return table.row( parentRow ).data();
} else {
return table.row($(scope).closest('tr')).data();
}
}
I recently introduced myself to using binding instead of self as a more structured and possibly memory efficient way. I tried to use it in my clickListener function by modifying it to the following:
clickListener(table: any, route: any) {
let rowData;
$('.display').on('click', 'tbody tr td .view', function(e) {
rowData = this.checkIfRowsHidden(table, this, e);
this.router.navigate([route + '/detail/' + rowData[0]]);
}.bind(this));
/*rest of the code
.
.
*/
Unfortunately, since I had been using multiple scopes, the this from the local and global scopes were being treated as the same, creating errors while retrieving data from the tables. Is there a way to bind global scope to only a specific items? or do I have to resort to var self=this?
You can use arrow functions to preserve global scope. Instead of using anonymous functions, you can switch to arrow functions, they automatically provide that:
$('.display').on('click', 'tbody tr td .view', (e) => {
rowData = self.checkIfRowsHidden(table, this, e);
self.router.navigate([route + '/detail/' + rowData[0]]);
});
This way, you don't need to bind the scope, instead this refers to the Angular component class.
Related
i'm trying to implement a text selection listener to display a toolbar for some custom options
<script>
export default {
name: "home",
created() {
document.onselectionchange = function() {
this.showMenu();
};
},
data() {
return {
...
};
},
methods: {
showMenu() {
console.log("show menu");
}
}
</script>
<style scoped>
</style>
but it still display that can't call showMenu of undefined, so i tried in this way:
created() {
vm = this;
document.onselectionchange = function() {
vm.showMenu();
};
},
so, nothing changed =(
i need to use this selectionchange because its the only listener that i can add that will handle desktop and mobile together, other method i should implement a touchup, touchdown and its not working for devices
Functions declared the classic way do have their own this. You can fix that by either explicitly binding this using Function.prototype.bind() or by using an ES6 arrow function (which does not have an own this, preserving the outer one).
The second problem is that if you have more than one of those components you've shown, each will re-assign (and thus, overwrite) the listener if you attach it using the assignment document.onselectionchange =. This would result in only the last select element working as you expect because it's the last one assigned.
To fix that, I suggest you use addEventListener() instead:
document.addEventListener('selectionchange', function() {
this.showMenu();
}.bind(this));
or
document.addEventListener('selectionchange', () => {
this.showMenu();
});
A third solution stores a reference to this and uses that in a closure:
const self = this;
document.addEventListener('selectionchange', function() {
self.showMenu();
});
I have this datatable that is created inside a function call. But if I want to create new rows based on event listeners like this, it gives an error that the table variable is undefined, which is understandable because it is inside a function call and not global. So how do I create a workaround for this and add event listeners under $(document).ready() section?
The structure of my JS file is very shabby, but it's intended to be in that way.
$(document).ready(function()
{
var table=null;
$('#button').click(function()
{
callfunction1();
}
callfunction1()
{
$.ajax({
'success': function(response) //response contains the plain table
{
createDatatable(response)
}
})
}
createDatatable(response)
{
$('#division').html(response); //creating the plain table
table=$('#tableId').Datatable({}); //converting it to datatable
}
//I want to add event listeners here, because if I add
//it anywhere else, it doesn't work, because basically
//it's a function call.
}
You can create a instance of the table var in a wider scope, example:
//the table is now a window object variable window.table
var table = null;
$(document).ready(function()
{
$('#button').click(function()
{
callfunction1();
}
callfunction1()
{
$.ajax({
'success': createDatatable()
})
}
createDatatable()
{
table=$('#tableId').Datatable({})
}
//the table is binded in the window scope so you can use in your event listeners
}
The following should work if you choose one var table declaration and delete the other
var table; //accessible everywhere
$(document).ready(function()
{
var table; //accessible anywhere in this function
$('#button').click(function() {
callfunction1();
}); //); were missing
function callfunction1 ()
{
$.ajax({
'success': createDatatable //no () here, you want to pass a function, not the result of a function call
});
}
function createDatatable()
{
table=$('#tableId').Datatable({});
}
}
This should give no errors, but i'm not sure if this is what you want to do.
So after all your answers(which I am very grateful for) , I have found out a different approach for it. As per documentation here, I added a $.ajax({}_.done() (this is as good as accessing the dataTable variable outside ajax call) function to host my event listener for accessing my dataTable.
Once again, I thank you all for the answers.
Edit: As requested for the correct solution.
$(document).ready(function()
{
var table=null;
$('#button').click(function()
{
callfunction1();
}
callfunction1()
{
$.ajax({
'success': function(response) //response contains the plain table
{
createDatatable(response)
}
}).done(function()
{
//add event listener here,You can access the table variable here. but you can not access the variable outside, instead just pass the variable to another function.
console.log(table);//this will work.
});
}
createDatatable(response)
{
$('#division').html(response); //creating the plain table
table=$('#tableId').Datatable({}); //converting it to datatable
}
}
I'm learning about Session and reactive data sources in Meteor JS. They work great for setting global UI states. However, I can't figure out how to scope them to a specific instance of a template.
Here's what I'm trying to do
I have multiple contenteditable elements on a page. Below each is an "Edit" button. When the user clicks on the Edit button, it should focus on the element and also show "Save" and "Cancel" buttons.
If the user clicks "Cancel", then any changes are eliminated, and the template instance should rerender with the original content.
Here's the code I have so far
// Helper
Template.form.helpers({
editState: function() {
return Session.get("editState");
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get("editState")) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", null);
},
});
// Template
<template name="form">
<div class="{{editState}}">
<p class="form-field" contenteditable>
{{descriptionText}}
</p>
</div>
Edit
Save
Cancel
</template>
// CSS
.edit-btn
.cancel-btn,
.save-btn {
display: inline-block;
}
.cancel-btn,
.save-btn {
display: none;
}
.is-editing .cancel-btn,
.is-editing .save-btn {
display: inline-block;
}
The problem
If I have more than one instance of the Form template, then .form-field gets focused for each one, instead of just the one being edited. How do I make so that only the one being edited gets focused?
You can render a template with data, which is basically just an object passed to it when inserted in to a page.
The data could simply be the key to use in the Session for editState.
eg, render the template with Template.form({editStateKey:'editState-topForm'})
you could make a handlebars helper eg,
Handlebars.registerHelper('formWithOptions',
function(editStateKey){
return Template.form({editStateKey:editStateKey})
});
then insert it in your template with
{{{formWithOptions 'editState-topForm'}}} (note the triple {, })
Next, change references from Session.x('editState') to Session.x(this.editStateKey)/ Session.x(this.data.editStateKey)
Template.form.helpers({
editState: function() {
return Session.get(this.editStateKey);
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get(this.data.editStateKey)) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, null);
},
});
Note: if you are using iron-router it has additional api's for passing data to templates.
Note2: In meteor 1.0 there is supposed to be better support for writing your own widgets. Which should allow better control over this sort of thing.
As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.
Alternative approaches
1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:
'click .js-edit-action': function(event, t) {
var $this = $(event.currentTarget),
container = $this.parents('.phenom-comment');
// open and focus
container.addClass('editing');
container.find('textarea').focus();
},
'click .js-confirm-delete-action': function(event, t) {
CardComments.remove(this._id);
},
2 ReactiveVar scoped to template instance
if (Meteor.isClient) {
Template.hello.created = function () {
// counter starts at 0
this.counter = new ReactiveVar(0);
};
Template.hello.helpers({
counter: function () {
return Template.instance().counter.get();
}
});
Template.hello.events({
'click button': function (event, template) {
// increment the counter when button is clicked
template.counter.set(template.counter.get() + 1);
}
});
}
http://meteorcapture.com/a-look-at-local-template-state/
3 Iron-Router's state variables
Get
Router.route('/posts/:_id', {name: 'post'});
PostController = RouteController.extend({
action: function () {
// set the reactive state variable "postId" with a value
// of the id from our url
this.state.set('postId', this.params._id);
this.render();
}
});
Set
Template.Post.helpers({
postId: function () {
var controller = Iron.controller();
// reactively return the value of postId
return controller.state.get('postId');
}
});
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#setting-reactive-state-variables
4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.
5 update the data context
Session is often the worse choice in my opinion. Also I don't personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as "Flow".
I'm using jQuery dataTables to display a table. I need to be able to pass a row selection event on to my Aura component that handles the selection and performs some operations on the data from that row.
In the initialize() function:
initialize: function()
{
$("#mytable tbody").click(function(event)
{
$(mytable.fnSettings().aoData).each(function ()
{
$(this.nTr).removeClass('row_selected');
});
$(event.target.parentNode).addClass('row_selected');
});
mytable = $('#mytable').dataTable();
},
I set up the click handler for the row selection, but how do I get a reference to the enclosing component so I can sandbox.emit() function to issue messages? I can put a reference to the component into the Closure, but that essentially makes this component a singleton and I could never have two instances of the component on the page at the same time.
Is there a standard way, using jQuery selectors or some other method, that I can retrieve a reference to the enclosing component from inside the click() handler?
Edit: I should never try to write code until I have had 32oz of caffine. You can pass a reference to the current component via the click() method itself. Like so:
$("#mytable tbody").click(this, function(event)
{
$(mytable.fnSettings().aoData).each(function ()
{
$(this.nTr).removeClass('row_selected');
});
$(event.target.parentNode).addClass('row_selected');
event.data.sandbox.emit('mychannel', {data: 'stuff'});
});
If I understand your question correctly, you could try something like this
initialize: function () {
var that = this;
$("#mytable tbody").click(function(event) {
//have acces to component as 'that'
});
}
what I used for events is view inside component configuration:
View: {
events: {
'click a[data-question-edit-id]': function (e) {
var button = $(e.currentTarget),
id = button.attr('data-question-edit-id'),
examId = this.component.examModel.get('id');
this.sandbox.router.navigate('/exams/' + examId + '/questions/' + id + '/edit', {trigger: true});
},
'click a[data-question-delete-id]': function (e) {
var button = $(e.currentTarget),
id = button.attr('data-question-delete-id');
this.component.showDeleteConfirmation(id);
}
}
}
If you'll find be helpful, here is my repo of aura project I'm working on:
https://github.com/lyubomyr-rudko/aura-test-project
At row level I catch the event and try to add an extra parameter
onRowClick: function(e){
console.log("Event in row");
e.model = "test";
console.log(e.model) // prints 'test'
}
In main view I catch the same event again
onRowClick: function(e){
console.log("Event in main view");
console.log(e.model) //prints undefined
}
Console:
>Event in row
>test
>Event in main view
>undefined
How can I append an attribute to the event?
The answer is that you don't catch the same event, but rather two (initially) identical events. Changing the first does not change the latter.
If you want to pass data between those events, you would need to store that data elsewhere (e.g. a closure, or if you don't care about the scope save it in the window object).
There are 2 ways that I know of to pass data to a jQuery event. One with with e.data, you can add any properties to e.data like this.
http://www.barneyb.com/barneyblog/2009/04/10/jquery-bind-data/
the other way is to use closures such as:
function myFunc() {
var model = 'test';
var x = {
onRowClick: function(e){
console.log("Event in row");
console.log(model) // prints 'test'
}
}
}
instead of catching the rowClick event in the main view, i suggest you catch it in the row view, and pass it through the backbone event system...
your parentview can bind to it's rows to catch a click.
there are two ways to do this,
trigger a custom event on your row's model, and let the parent bind to every model in the collection, though that seems like a hack and a performance hit.
i suggest doing it with an event aggregator:
var App = {
events: _.extend({}, Backbone.Events);
};
var myGeneralView = Backbone.Views.extend({
initialize: function() {
_.bindAll(this, "catchMyCustomEvent";
/*
and here you bind to that event on the event aggregator and
tell it to execute your custom made function when it is triggered.
You can name it any way you want, you can namespace
custom events with a prefix and a ':'.
*/
App.events.bind('rowView:rowClicked');
},
catchMyCustomEvent: function (model, e) {
alert("this is the model that was clicked: " + model.get("myproperty"));
}
// other methods you will probably have here...
});
var myRowView = Backbone.Views.extend({
tagName: "li",
className: "document-row",
events: {
"click" : "myRowClicked"
},
initialize: function() {
_.bindAll(this, "myRowClicked");
},
myRowClicked: function (e) {
/*
You pass your model and your event to the event aggregator
*/
App.events.trigger('rowView:rowClicked', this.model, e);
}
});