I am using angular-datatables. There is a column Actions which I render using an inline template. I want to access current row data in that template. How do that?
controller
$scope.dtOptions = DTOptionsBuilder.newOptions().withOption('ajax', {
url: '/api/department',
type: 'GET'
})
.withDataProp('data')
.withOption('processing', true)
.withOption('serverSide', true)
.withPaginationType('full_numbers')
.withOption('createdRow', function (row, data, dataIndex) {
return $timeout(function() {
// Recompiling so we can bind Angular directive to the DT
return $scope.$apply($compile(angular.element(row).contents())($scope));
});
})
.withBootstrap();
$scope.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('name').withTitle('Name'),
DTColumnBuilder.newColumn('actions').withTitle('Actions').withOption("searchable", false)
];
view
<div class="hbox hbox-auto-xs hbox-auto-sm" ng-controller="DepartmentsController">
<!-- Inline Template -->
<script type="text/ng-template" id="actions.html">
<button class="btn btn-primary btn-xs"
ng-click="edit(/** CURRENT ROW ELEMENT ID */)"><i class="fa fa-edit"></i> Edit</button>
<button class="btn btn-danger btn-xs"
ng-click="delete()"><i class="fa fa-trash"></i> Delete</button>
</script>
<div class="bg-light lter b-b wrapper-md">
<h1 class="m-n font-thin h3">Departments</h1>
</div>
<div class="wrapper-md">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-xs-6">
<button class="btn m-b-md btn-md btn-primary " ui-sref="manager.departments.create">
<i class="fa fa-plus"></i> <span class="hidden-sm hidden-xs">Add Department</span></button>
</div>
</div>
<div class="row">
<div class="col-sm-12 m-b-xs">
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns" class="table table-striped b-t b-b">
<thead>
<tr>
<th style="width:20%">ID</th>
<th style="width:60%">Name</th>
<th style="width:20%">Actions</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
Here is plunkr to help you: http://plnkr.co/edit/iAZBof7g6cp68RnM0X8H?p=preview
After hours of struggle, I found the solution. It's quite obvious when you see it.
I created a new scope and added data to it before passing it to $compile in createRow callback. Creating a new scope is necessary to pass unique data to each row. If you simply passed by $scope.row then each row will have the same row equal to the last row processed.
controller
.withOption('createdRow', function (row, data, dataIndex) {
// Create a new scope for each row, otherwise, data will
// not be unique for each row becuase of data bindings
var $newScope = $scope.$new(true);
$newScope.row = data;
// Pass any methods you are using in current scope
$newScope.delete = $scope.delete;
return $timeout(function() {
// Recompiling so we can bind Angular directive to the DT
return $scope.$apply($compile(angular.element(row).contents())($newScope));
});
});
view
<script type="text/ng-template" id="actions.html">
<button class="btn btn-primary btn-xs" ui-sref="manager.departments.edit({id: {{ row.id }} } )"><i class="fa fa-edit"></i> Edit</button>
<button class="btn btn-danger btn-xs" ng-bootbox-confirm="Are you sure you want to delete this department?" ng-bootbox-confirm-action="delete(row.id)"><i class="fa fa-trash"></i> Delete</button>
</script>
I used the above accepted answer, and it worked fine. However, later when we moved to production and the rows per page changed from 20 to 500, I saw significant performance issues through chrome developer tools (Most time spent on hundreds of setTimer and listener events)
I found the official document here which gives us an example as below:
.withOption('createdRow', createdRow);
// ...
function createdRow(row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
}
This piece of code does not use the $timeout or $apply functions, but still works well. If you run into performance issues as I did, this may help.
Related
I'm trying to route from an index list of items to a page that will display a detailed view of that item.
In my index view I have a table that iterates through all the items that are saved in the database.
There is a button under the actions column that will take me to events/show route using ng-click="go('events/show')"
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Title</th>
<th class="col-md-2">Actions</th>
</tr>
</thead>
<tbody>
<tr scope="row" ng-repeat="event in events | reverse | filter:filterByUID">
<td>{{event.title}}</td>
<td class="col-md-2">
<div class="btn-group" role="group" aria-label="actions">
<button class="btn btn-primary" ng-click="go('events/show')">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
</button>
<button class="btn btn-primary" ng-click="events.$remove(event)">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
The table looks like this:
In my controller I have:
$scope.go = function ( path ) {
$location.path( path );
};
in my routes.js I have:
.whenAuthenticated('/events/show', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
Everything works so far.
However, what is unclear to me is how do I pass the event id to the eventShow.html page, so I know which item was clicked from the index list, so I can display the detailed information?
My firebase database looks like this:
Check out ui-router, it makes dynamic routing much easier
https://github.com/angular-ui/ui-router
But if you want to keep what you have, you should pass the event id into your path, like such
$scope.go = function ( path, event ) {
$location.path( path + "/" + event.id );
};
.whenAuthenticated('/events/show/:eventId', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
and in your controller, access $stateParams.eventId to load that event.
You should use a variable in your router:
.whenAuthenticated('/events/:id', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
Then you can simply use the ID in your function call:
go('events/:id')
Here's a great tutorial (and I highly recommend watching all of both parts).
And you'll have nicer URLs that can be bookmarked.
One you could pass the UID(uid is just an example for user id) onClick
<tr scope="row" ng-repeat="event in events | reverse | filter:filterByUID">
<td>{{event.title}}</td>
<td class="col-md-2">
<div class="btn-group" role="group" aria-label="actions">
<button class="btn btn-primary" ng-click="go('events/show', event.UID)">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
</button>
<button class="btn btn-primary" ng-click="events.$remove(event)">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</div>
</td>
</tr>
Then in your js file
$scope.go = function ( path, uid ) {
$location.path( path + "/" + uid );
};
.whenAuthenticated('/events/show/:eventId', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
Then to query firebase, say you have a field in your objects called uid, you can use startAT and endAT methods.
See here for example
And here to read more on filtering
I am working with AngularJs+DataTable library, and I wish to create a custom control that can apply a exactly search function from DataTable, but with custom UI and control. However, the serch() return 0 length result which no consist any string value and the draw() isn't call properly.
I have follow some similar question on github, article and implement with $scope.dtInstance.DataTable.search(...).draw();
but turn out, it wouldn't working, so below is what I try, but same result.
Any suggestion?
Here is my HTML implementation
<button class="btn btn-white btn-sm" type="button"
data-toggle="collapse" data-target="#collapseSearch"
aria-expanded="false"
aria-controls="collapseSearch">
<i class="fa fa-search"></i> Search
</button>
<div class="collapse" id="collapseSearch">
<div class="row margin-top-20px">
<div class="col-sm-12 margin-bottom-5px">
<div class="input-group bookingRecordDataTable_filter dataTables_filter">
<span class="input-group-addon input-addon-green">Search</span>
<input type="search" class="form-control"
ng-model="searchText"
ng-change="searchTable()"
placeholder="search"
aria-controls="bookingRecordDataTable">
</div>
</div>
</div>
</div>
<table datatable="ng"
class="table table-hover"
dt-options="dtOptions"
dt-column-defs="dtColumnDefs" id="bookingRecordDataTable"
dt-instance="dtInstanceCallback">
</table>
Here is the angular controller
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('bInfo', false)
.withOption('bFilter', false)
.withOption('bAutoWidth', false)
.withOption('bLengthChange', false)
.withDOM("<'col-sm-12't><'col-sm-12'p>")
.withOption('order', [0, 'desc'])
.withBootstrap();
$scope.dtColumnDefs = [
DTColumnDefBuilder.newColumnDef(0).withTitle('Id').notVisible(),
...
];
$scope.dtInstanceCallback = function(dtInstance)
{
var datatableObj = dtInstance;
$scope.tableInstance = datatableObj;
}
$scope.searchTable = function ()
{
console.log($scope.tableInstance);
var query = $scope.searchText;
console.log(query);
var result = $scope.tableInstance.DataTable.search(query, false, false, false);
console.log(result);
$scope.tableInstance.DataTable.search(query, false, false, true).draw();
};
finally, I found out this part of implementation work for me, share it out if anyone also face same issues.
$scope.dtInstance = {};
$scope.searchTable = function ()
{
$scope.dtInstance.DataTable.search($scope.searchText);
$scope.dtInstance.DataTable.search($scope.searchText).draw();
};
I'm currently building a nifty little 'talent point distributor' view, similar to what popular RPG games offer. I didn't want a huge wall of HTML code for all the buttons and textboxes, so I created a template to which I pass two parameters:
the name of the stat I want to alter
the initial value of the stat
The template renders correctly, and I notice that when I log the results to the console, the variable seems to be changed correctly. However, the displayed value does not change and will always stay at 0.
Here is the template itself:
<template name="attributeStepper">
<div class="row" style="margin: 1em;">
<div class="col-sm-3 col-md-2">
<h4>{{toUpper attribute}}</h4>
</div>
<div class="col-sm-6 col-md-4">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-value-dec">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
<button type="button" class="btn btn-default disabled">{{attributeValue}}</button>
<button type="button" class="btn btn-default btn-value-inc">
<span class="glyphicon glyphicon-chevron-up"></span>
</button>
</div>
</div>
</div>
</template>
Here is the helper I defined for the template:
Template.attributeStepper.helpers({
toUpper : function(str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
})
Template.attributeStepper.events({
'click .btn-value-inc' : function(event, tmpl) {
tmpl.data.attributeValue ++;
},
'click .btn-value-dec' : function(event, tmpl) {
tmpl.data.attributeValue --;
}
});
And this is how I call the templates from the actual view:
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Attributes</h3>
</div>
{{ >attributeStepper attribute="strength" attributeValue="0"}}
{{ >attributeStepper attribute="courage" attributeValue="0"}}
{{ >attributeStepper attribute="intelligence" attributeValue="0"}}
{{ >attributeStepper attribute="agility" attributeValue="0"}}
{{ >attributeStepper attribute="dexterity" attributeValue="0"}}
{{ >attributeStepper attribute="intuition" attributeValue="0"}}
{{ >attributeStepper attribute="charisma" attributeValue="0"}}
</div>
I hope you can make any sense out of this and tell me what I'm doing wrong, because I feel like I'm not following the mindset behind Meteor correctly yet.
Cheers!
There is nothing wrong but also nothing reactive in your code. For the attributeValue you should use a template based ReactiveVar which is created at the onCreate Event
Template.attributeStepper.onCreated(function() {
if (! _.isUndefined(this.data.startingValue))
this.attributeValue = new ReactiveVar(Number(this.data.startingValue));
else
this.attributeValue = new ReactiveVar(0);
})
You can use some initialValue from Template as you like
See complete example at the MeteorPad I created for you.
http://meteorpad.com/pad/Zw7YnnW57uuGKcu3Q/MultipleTemplateUsage
This should solve your question
Cheers
Tom
Do you have idea about reactive-var in meteor (Meteor Doc) or you can also use Session instead of reactive-var (ReactiveVar is similar to a Session variable)
Have a look at changes as per your code.
Here is the template(.html)
<template name="attributeStepper">
<div class="row" style="margin: 1em;">
<div class="col-sm-3 col-md-2">
<h4>{{toUpper attribute}}</h4>
</div>
<div class="col-sm-6 col-md-4">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-value-dec">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
<button type="button" class="btn btn-default disabled">{{getAttributeValue}}</button>
<button type="button" class="btn btn-default btn-value-inc">
<span class="glyphicon glyphicon-chevron-up"></span>
</button>
</div>
</div>
</div>
</template>
Here is helpers for your template(.js)
Template.attributeStepper.created = function(){
this.attributeValue = new ReactiveVar(parseInt(this.data.attributeValue));
}
Template.attributeStepper.helpers({
toUpper : function(str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
},
getAttributeValue : function(){
return Template.instance().attributeValue.get();
}
});
Template.attributeStepper.events({
'click .btn-value-inc' : function(event, tmpl) {
tmpl.attributeValue.set(tmpl.attributeValue.get()+1)
},
'click .btn-value-dec' : function(event, tmpl) {
tmpl.attributeValue.set(tmpl.attributeValue.get()-1)
}
});
Template.attributeStepper.created = function(){...} method called before your template's logic is evaluated for the first time.
I am writing a Laravel 5 project with a comment section code below
#foreach($Comment as $Comment)
<div id="comment-{!! $Comment->comments_id !!}" class="comment-wrapper">
<div class="btn btn-lg btn-info btn-xs" class="show">Show</div>
<div class="btn btn-lg btn-success btn-xs" class="hide">Hide</div>
<div class="btn btn-lg btn-warning btn-xs" class="toggle">Toggle</div>
<div class="watch" class="jumbotron alert-info">
<ul class="list-group">
<li class="list-group-item list-group-item-success">{!! $Comment->author !!}</li>
<li class="list-group-item"> {!! $Comment->text !!}</li>
</ul>
#if ($Comment->author == Auth::user()->name)
<p>Delete</p>
#endif
<h6><small>CREATED ON: {!! $Comment->created_at !!}</small></h6>
</div>
</div>
#endforeach
and I have a javascript file which looks like this
$(document).ready(function () {
$('.show').click(function () {
$(this).closest('.comment-parent').find('.watch').show('slow');
});
$('.hide').click(function () {
$(this).closest('.comment-parent').find('.watch').hide('slow');
});
$('.toggle').click(function () {
$(this).closest('.comment-parent').find('.watch').toggle('slow');
});
});
The trouble is the toggle/hide javascript function only works on one set of buttons and hides all of the comments. I want to have the set of buttons that work for each comment individually. I've tried to increment the watch class and buttons div id by adding 1 and incrementing it for each comment but can't get it to work. Any help would be appreciated thanks.
You may try something like this:
$('#show').click(function () {
$(this).next('.watch').show('slow');
});
Try same approach for other methods, so only the next first div with class of watch will be acted and also, you could have wrapped each set in a single parent container using a unique id attribute in addition to a class, for better grouping. For example:
#foreach($Comment as $Comment)
<div id="comment-{{$comment->id}}" class="comment-wrapper">
<div class="btn btn-lg btn-info btn-xs show">Show</div>
<!-- More... -->
<div class="watch" class="jumbotron alert-info">
<!-- More... -->
</div>
</div>
#endforeach
This way, you could have done the jQuery slecting more specifically, for example:
$('#show').click(function () {
$(this).closest('.comment-parent').find('.watch').show('slow');
});
Update (Thanks to haakym for pointing me that): Also, an id must be unique so instead of using id='show' use it as a class and then use:
$('.show').click(function () {
$(this).closest('.comment-parent').find('.watch').show('slow');
});
I am writing a messaging app which has a delete / edit function for a user for a given message that is submitted. What I would like to do is write something like:
{{#if currentUser._id === this._id}}
<!-- Show -->
{{/if}}
But this is probably wrong I have a template written for the message record:
<template name="message">
<div class="row message-row">
<div class="col-md-12">
<div class="message-container">
<div class="message-avatar">
<img src="{{userAvatar}}">
</div>
<p>{{message}}</p>
<div class="message-time">{{prettifyDate time}}</div>
<!-- this is the div to hide / show based on the conditional -->
<div class="message-controls">
<button class="btn btn-link btn-xs" type="button" id="deleteMessage"><i class="fa fa-trash-o"></i></button>
<button class="btn btn-link btn-xs" type="button" id="editMessage"><i class="fa fa-edit"></i></button>
</div>
<!-- end -->
</div>
</div>
</div>
</template>
And I am using the following in my client.js
Template.messages.messages = function() {
return Messages.find({}, { sort: {time: -1} });
}
But at this point I am stuck
Assuming your message documents have a userId field, you can simply do this:
Template.message.helpers({
isOwner: function() {
return this.userId === Meteor.userId();
}
});
and:
{{#if isOwner}}
<!-- controls -->
{{/if}}
You could also make a more flexible, reusable global helper for this:
Template.registerHelper('isCurrentUser', function(userId) {
return userId === Meteor.userId();
});
<!-- in this example, the document might have an `ownerId` field rather than `userId` -->
{{#if isCurrentUser ownerId}}{{/if}}
Of course, you also need to validate the updates on the server with the allow/deny API or with custom methods.