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();
};
Related
I have a requirement where I want to call an AngularJS function on button click. So I tried like below
var app2 = angular.module('grdContrl', ['datatables']);
app2.controller('dtAssignVendor', ['$scope', '$http', 'DTOptionsBuilder', 'DTColumnBuilder',
function ($scope, $http, DTOptionsBuilder, DTColumnBuilder) {
$scope.GetFiler = function () {
var strZone = $('#SAPExecutive_R4GState').val();
var strUtility = $('#ddlUtility').val();
$scope.dtColumns = [
DTColumnBuilder.newColumn(null, '').renderWith(function (data, type, full) {
return '<input type="checkbox" class="check" data-object-id="' + full.objectid + '">'
}),
DTColumnBuilder.newColumn("MAINTENANCEZONENAME", "MAINTENANCEZONENAME"),
DTColumnBuilder.newColumn("MAINTENANCEZONECODE", "MAINTENANCEZONECODE")
]
$scope.dtOptions = DTOptionsBuilder.newOptions().withOption('ajax', {
url: AppConfig.PrefixURL + "/App/GetMPFilter",
type: "POST",
data: JSON.stringify({ strZone: strZone, strUtility: strUtility }),
})
.withPaginationType('full_numbers')
.withDisplayLength(10);
}
}])
<button class="btn btn-default customBtn" ng-click="GetFilter();">
<i class="fa fa-filter" aria-hidden="true"></i>
Filter
</button>
---------------------------
<div ng-app="grdContrl">
<div class="flTable" id="dtAssignVendor">
<table id="assignVender" class="mp myTable table table-striped table-bordered" cellspacing="0" width="100%">
</table>
</div>
</div>
I want this to work on the above mentioned button click.
You can not get filter values, because filter button is outside of controller and if you are using datatable then also add dt-option, dt-columns and dt-instance into <table> as attributes.
You have do like this way
<div ng-app="grdContrl" ng-controller="dtAssignVendor"> <!-- COMMON ANCESTOR-->
<button class="btn btn-default customBtn" ng-click="GetFilter();">
<i class="fa fa-filter" aria-hidden="true"></i> Filter
</button>
<div class="flTable" id="dtAssignVendor">
<table id="assignVender" class="mp myTable table table-striped table-bordered" cellspacing="0" width="100%" datatable="" dt-options="dtOptions" dt-columns="dtColumns" dt-instance="dtInstanceNonInvProduct">
</table>
</div>
</div>
Also inject into controller.
app2.controller('dtAssignVendor',function ($scope, $http, DTOptionsBuilder, DTColumnBuilder,DTInstances) {
$scope.GetFiler = function () {
//get input values into scope instead of javascript variable
//var strZone = $('#SAPExecutive_R4GState').val();
//var strUtility = $('#ddlUtility').val();
$scope.strZone = $scope.SAPExecutive_R4GState;
$scope.strUtility = $scope.ddlUtility;
//redraw table on button click
$scope.dtInstanceNonInvProduct.DataTable.draw();
}
$scope.dtOptions = DTOptionsBuilder.newOptions().withOption('ajax', {
url: AppConfig.PrefixURL + "/App/GetMPFilter",
type: "POST",
data: JSON.stringify({ strZone: $scope.strZone, strUtility: $scope.strUtility }),
})
.withPaginationType('full_numbers')
.withDisplayLength(10);
$scope.dtColumns = [
DTColumnBuilder.newColumn(null, '').renderWith(function (data, type, full) {
return '<input type="checkbox" class="check" data-object-id="' + full.objectid + '">'
}),
DTColumnBuilder.newColumn("MAINTENANCEZONENAME", "MAINTENANCEZONENAME"),
DTColumnBuilder.newColumn("MAINTENANCEZONECODE", "MAINTENANCEZONECODE")
]
$scope.dtInstanceNonInvProduct = {};
})
The template of your code should be something like this:
<div ng-app="myApp">
<div controller="ctrl1">
<button ng-click="someFunInCtrl1()">Click Me</button>
...
</div>
<div controller="ctrl2">
<button ng-click="someFunInCtrl2()">Click Me</button>
...
</div>
</div>
You are unable to call GetFilter function because you are calling it from outside controller scope. As #Karim said
you need to use the ng-controller directive and wrap the button inside it
As far as i see you did not bind the controller to your template, you need to use the ng-controller directive and wrap the button inside it (and correct the spelling error on $scope.getFilter as raised in the comments)
<div ng-app="grdContrl" ng-controller="dtAssignVendor"> <!-- COMMON ANCESTOR-->
<button class="btn btn-default customBtn" ng-click="GetFilter();">
<i class="fa fa-filter" aria-hidden="true"></i> Filter
</button>
---------------------------
<div class="flTable" id="dtAssignVendor">
<table id="assignVender" class="mp myTable table table-striped table-bordered" cellspacing="0" width="100%">
</table>
</div>
</div>
How could I combine the two models which i have kept for two different purpose. One is to for file upload and the other is for render data from different object.Below is the respective html and JS i tried.
HTML section
<div class="well" data-bind="fileDrag: fileData">
<div class="form-group row">
<div class="col-md-6">
<img style="height: 125px;" class="img-rounded thumb" data-bind="attr: { src: fileData().dataURL }, visible: fileData().dataURL">
<div data-bind="ifnot: fileData().dataURL">
<label class="drag-label">Drag file here</label>
</div>
</div>
<div class="col-md-6">
<input type="file" data-bind="fileInput: fileData, customFileInput: {
buttonClass: 'btn btn-success',
fileNameClass: 'disabled form-control',
onClear: onClear,
}" accept="application/pdf,image/*">
</div>
</div>
</div>
<button class="btn btn-default" data-bind="click: debug">Upload</button>
</div>
<div id="notification" style="display: none;">
<span class="dismiss"><a title="dismiss this notification">X</a></span>
</div>
<!-- Collapsible Panels - START -->
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Plan Details</h3>
</div>
<div class="panel-body">
<span class="glyphicon glyphicon-plus clickable" id="addPlanBtn"></span>
<span class="glyphicon glyphicon-remove clickable" id="removePlanBtn"></span>
<span class="glyphicon glyphicon-edit clickable" id="editPlanBtn"></span>
<table id="docsDataTable" class="table table-striped display" width="100%">
<thead>
<tr>
<th></th>
<th>Contract Document ID</th>
<th>Contract ID</th>
<th>Document Name</th>
<th>File Path</th>
<th>Comments</th>
</tr>
</thead>
</table>
<div class="modal fade" id="notificationDialog" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" \>×</button>
<h4 class="modal-title">Notification</h4>
</div>
<div class="modal-body" id="notificationBody">
</div>
<div class = "modal-footer">
<button type = "button" class = "btn btn-primary" data-dismiss = "modal">
Ok
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="confirmBox" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" \>×</button>
<h4 class="modal-title">Confirmation</h4>
</div>
<div class="modal-body" id="confirmBody">
Selected rows will be made inactive.
</div>
<div class = "modal-footer">
<button type = "button" class = "btn btn-default" data-dismiss = "modal" id="confirmNoBtn">
Cancel
</button>
<button type = "button" class = "btn btn-primary" data-dismiss = "modal" id="confirmYesBtn">
Ok
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Javascript section to bind the data
var dataset;
var docsModel;
var docsTable;
var vasTypes;
$(function(){
var viewModel = {};
viewModel.fileData = ko.observable({
dataURL: ko.observable(),
// base64String: ko.observable(),
});
viewModel.onClear = function(fileData){
if(confirm('Are you sure?')){
fileData.clear && fileData.clear();
}
};
viewModel.debug = function(){
window.viewModel = viewModel;
//console.log(ko.toJSON(viewModel));
fileUpload(viewModel);
debugger;
};
ko.applyBindings(viewModel);
});
$(document).ready(function(){
docsModel = new $.cordys.model({
context: document.getElementById("addPanelForm"),
objectName: "CONTRACT_DOCUMENT",
fields: ["CONTRACT_DOCUMENT_ID" , "CONTRACT_ID" , "DOCUMENT_NAME" , "FILE_PATH" , "COMMENTS"],
defaults: {
namespace: "http://services.vw.com/lpms/1.0/wsapp"
},/*
update: {
method: "UpdatePlanVas"
},*/
read: {
method: "GetContractDocumentObjectsForContractId",
parameters: {
CONTRACT_ID: "CONTRACT_1000"
},
}
});
GetContractDocumentObjectsForContractId();
docsTable = $('#docsDataTable').DataTable({
data: dataset,
columnDefs: [ {
orderable: false,
className: 'select-checkbox',
defaultContent: "",
targets: 0},
{ data: "CONTRACT_DOCUMENT_ID",
targets: 1,
visible: false},
{ data: "CONTRACT_ID",
targets: 2},
{ data: "DOCUMENT_NAME",
targets: 3},
{ data: "COMMENTS",
targets: 5},
{ data: "FILE_PATH",
targets: 4}],
select: {
style: 'multi',
selector: 'td:first-child'
},
order: [[ 1, 'asc' ]],
"searching": false,
"lengthChange": false
});
});
function fileUpload(data){
dataObject=ko.toJS(viewModel);
fileName=dataObject.fileData.file.name;
fileContent=dataObject.fileData.dataURL.split(',')[1];
$.cordys.ajax({
method: "WriteFile",
parameters: {
filename: fileName,
encoded: "true",
data:fileContent
},
namespace:"http://schemas.cordys.com/1.0/ac/FileConnector",
dataType: "* json",
async: false,
success: function(e){
$.notify("Yeah ! File Uploaded", "success");
}
});
}
You're getting the error
You cannot apply bindings multiple times to the same element
Because Knockout only permits one view-model to be bound to a DOM element.
In your case, you need to somehow combine the two view-models into one. While you could simply add the properties from one view-model into the other, perhaps creating a third view-model with a new name so you can continue using the original form of these view-models elsewhere, my suggestion would be to create a new super view-model, and reference the two existing view-models as properties on this new view-model.
At this point I would normally create an example from the code in the OP but in this case, as #Jeroen has pointed out, it's rather difficult to make out what's going on in the OP. As far as I can see, there's only one view-model in there while your question revolves around having two view-models. So the following example is unfortunately very generic.
var ViewModel1 = function() {
var self = this;
self.obs1_1 = ko.observable();
self.obs1_2 = ko.observableArray([]);
// some initialisation stuff
},
ViewModel2 = function() {
var self = this;
self.obs2_1 = ko.observable();
self.obs2_2 = ko.observableArray([]);
// some initialisation stuff
},
SuperViewModel = function() {
var self = this;
self.vm1 = new ViewModel1();
self.vm2 = new ViewModel2();
// some initialisation stuff
};
You would then instantiate and data-bind SuperViewModel, and reference the observables like so
<input type="text" data-bind="textInput: vm1.obs1_1" />
<div data-bind="foreach: vm1.obs1_2">
<span data-bind="html: $data"></span>
</div>
or to make typing a little bit easier
<!-- ko with: vm1 -->
<input type="text" data-bind="textInput: obs1_1" /> <!-- this time without "vm1." -->
<div data-bind="foreach: obs1_2"> <!-- this time without "vm1." -->
<span data-bind="html: $data"></span>
</div>
<!-- /ko -->
You now have a single view-model, SuperViewModel, referencing your unchanged existing view-models. This solution allows you to leave existing JavaScript and views while offering an easy method of data-binding the functionality of multiple view-models inside a single view-model.
It's technically possible to achieve a similar result by doing some referencing at the prototype level, but that could quickly cause complications and the only advantage would be saving you from typing the name of a property.
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.
So the situation is as follows:
I have an input bar where a user can search up a business name or add a person's name (and button to select either choice). Upon hitting enter I want to append a unique instance of a template (with the information entered by the user added). I have 2 templates I've created depending of if the user is searching for a business or a person.
One approach I've thought about is creating an object with the data and adding it with ng-repeat, however I can't seem to get the data loaded, and even then don't know how I can store reference to a template in my collection.
The other idea I've come across is adding a custom directive. But even then I've yet to see an example where someone keeps appending a new instance of a template with different data.
Here is the code so far:
payments.js:
angular.module('payment-App.payments',['ngAutocomplete'])
.controller('paymentController', function($scope, $templateRequest/*, $cookieStore*/) {
$scope.businessID;
$scope.address;
$scope.isBusiness = false;
$scope.payees = [];
$scope.newPayee = function () {
this.businessID = $scope.businessID;
this.address = $scope.address;
}
$scope.submit = function () {
var text = document.getElementById("businessID").value.split(",");
$scope.businessID = text[0];
$scope.address = text.slice(1).join("");
$scope.newPayee();
}
$scope.addPayee = function () {
$scope.submit();
$scope.payees.push(new $scope.newPayee());
console.log($scope.payees);
}
$scope.selectBusiness = function () {
//turns on autocomplete;
$scope.isBusiness = true;
}
$scope.selectPerson = function () {
//turns off autocomplete
$scope.isBusiness = false;
}
$scope.fillAddress = function () {
// body...
}
})
.directive("topbar", function(){
return {
restrict: "A",
templateUrl: 'templates/businessTemplate.html',
replace: true,
transclude: false,
scope: {
businessID: '=topbar'
}
}
})
payments.html
<h1>Payments</h1>
<section ng-controller="paymentController">
<div>
<div class="ui center aligned grid">
<div class="ui buttons">
<button class="ui button" ng-click="selectBusiness()">Business</button>
<button class="ui button arrow" ng-click="selectPerson()">Person</button>
</div>
<div class="ui input" ng-keypress="submit()">
<input id="businessID" type="text" ng-autocomplete ng-model="autocomplete">
</div>
<div class="submit">
<button class="ui button" id="submit" ng-click="addPayee()">
<i class="arrow right icon"></i>
</button>
</div>
</div>
<div class="search"></div>
<div class="payments" ng-controller="paymentController">
<li ng-repeat="newPayee in payees">{{payees}}</li>
</div>
<!-- <topbar></topbar> -->
</div>
(example template)
businessTemplate.html:
<div class="Business">
<div class="BusinessName" id="name">{{businessID}}</div>
<div class="Address" id="address">{{address}}</div>
<button class="ui icon button" id="hoverbox">
<i class="dollar icon"></i>
</button>
</div>
I ended up using a workaround with ng-repeat here. Still curious about the original question though.
I am making an app, the user can either select an item or use their camera to get the qr code which translates into an item's ID.
The problem is that I think some JQuery is messing with my scope from working properly.
I have to get the QR code by listening to an innerHtml change, once it changes (DOMSubtreeModified) the following occurs.
var index = 0;
$('#result').one('DOMSubtreeModified', function(e) {
var code = "";
if (e.target.innerHTML.length > 0) {
code = e.target.innerHTML;
$scope.ToRun(code);
}
});
$scope.ToRun = function(code) {
for (i = 0; i < $scope.plantList.length; i++) {
if ($scope.plantList[i].plantcode == code) {
index = i;
break;
}
}
$scope.currentPlant = $scope.plantList[index];
$scope.plantDetails = false;
$scope.searchDetails = true;
}
For some reason the following does not have any effect on my ng-classes. As when an item is selected I hide the input dialogs, and show the result one.
$scope.plantDetails = false;
$scope.searchDetails = true;
But when a user selects the item manually it works just perfectly. (the items have an ng-click on it)
$scope.viewPlant = function(plant) {
$scope.currentPlant = plant
$scope.plantDetails = false;
$scope.searchDetails = true;
};
And the above works fine, with the ng-click. So why won't my new function that listens for an innerHtml change work?
HTML snippet
<div ng-class="{ 'hidden': searchDetails }">
<!-- CHOOSE INPUT TYPE -->
<div class="form-group" style="margin-bottom:0px">
<div class="btn-group btn-group-justified">
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="digits = false; qr = true">Plant Code</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="digits = true; qr = false">QR Code</button>
</div>
</div>
</div>
<br />
<!-- QR CODE INPUT -->
<div ng-class="{ 'hidden': qr }">
<img id="blah" src="./images/placeholder.png" />
<span class="btn btn-default btn-file">
<i class="glyphicon glyphicon-camera"></i>
<input type="file" onchange="readURL(this);handleFiles(this.files)">
</span>
<div id="result">xxxxxx</div>
<canvas id="qr-canvas" width="800" height="600"></canvas>
</div>
<!-- MANUAL SELECTION INPUT -->
<div ng-class="{ 'hidden': digits }">
<input ng-model="search.$" style="width:100%; font-size:30px; text-align:center" placeholder="Plant Code" />
<div style="overflow: auto; max-height:250px">
<table class="table table-striped" style="background-color:lightblue">
<tr ng-repeat="plant in plantList | filter:search" ng-click="viewPlant(plant)" style="cursor:pointer">
<th>{{plant.name}}</th>
<th>{{plant.plantcode}}</th>
</tr>
</table>
</div>
</div>
<div>
</div>
</div>
<div ng-class="{ 'hidden': plantDetails }">
// results - this should appear when one of the above is entered.
// works for manual selection, but not qr code
</div>
Just confused on why my QR input will not fire off the change-class options to hide the searchDetails div and show the plantDetails div
EDIT: Doing a small test, it seems that my class variables are indeed not updating at all. I just put the values at the bottom of my page and they do not update when hitting the block of:
$scope.plantDetails = false;
$scope.searchDetails = true;
You need to let Angular know about the change. Add this at the end of your method and let me know if it works.
$scope.$apply();