I'm using an angular library that's called mdDataTable to handle my tables with searching, paging and so on.
I am trying to reproduce the functionality from the "Ajax Search Support" demo on this demo page, but I can't get it to work. Searching works fine, but my paging will only show that one page is avalible even if the results is eleven items and my pageSize is set to 5 per page.
I'm not sure how to simulate an api-call on codepen so I'll paste the code.
I also depend on Angular material and $resource. $resource is my "dataService" and then I have a service called paginationHandler, that's really just picks up pagination information in the response headers from the server (Custom header) and stores it for the application to use. I got an interceptor that handles storing that data.
My view containing the table:
<md-card class="bg-white" layout-fill md-whiteframe="4db">
<md-input-container class="padder-md m-t-xl">
<label style="padding-left: 20px; padding-right: 20px;">Sök användare</label>
<input type="text" ng-model="filterText">
</md-input-container>
<mdt-table paginated-rows="{isEnabled: true, rowsPerPageValues: [5,10,20,50,100]}"
mdt-row-paginator="vm.paginatorCallback(page, pageSize)"
sortable-columns="true"
animate-sort-icon="true"
mdt-row-paginator-no-results-message="Inga resultat hittades..."
mdt-row-paginator-error-message="Fel uppstod vid hämtning av maskinmodeller."
mdt-trigger-request="vm.getLoadResultsCallback(loadPageCallback)"
mdt-row="{
'table-row-id-key': 'id',
'column-keys': [
'name',
'category',
'machineModelType.name',
'createdDate',
'remove'
],
}">
<mdt-header-row>
<mdt-column align-rule="left">Namn</mdt-column>
<mdt-column align-rule="left">Kategori</mdt-column>
<mdt-column align-rule="left">Typ</mdt-column>
<mdt-column align-rule="left">Skapad</mdt-column>
<mdt-column align-rule="left">Ta bort</mdt-column>
</mdt-header-row>
</mdt-table>
</md-card>
And this is my controller. It's a route in ui-router that has the controller defined in that route "as vm", so I haven't forgotten to link the controller to the view.
(function () {
"use strict";
angular.module("app")
.controller("detailsManufacturerCtrl", [
"$stateParams",
"dataService",
"$scope",
"paginationHandler",
function ($stateParams, dataService, $scope, paginationHandler) {
var vm = this;
vm.machineModels = [];
// Data retrieval
vm.getMachineModels = function(){
dataService.manufacturerMachineModels.query({manufacturerId: $stateParams.id, sort: "name", page: 1, pageSize: 5, search: ""}, function(response){
vm.machineModels = response;
vm.pagination = paginationHandler.get();
});
};
// Machine Model Table Functions
vm.paginatorCallback = function(page, pageSize){
var query = $scope.filterText ? $scope.filterText : '';
return uow.manufacturerMachineModels.query({
manufacturerId: $stateParams.id,
sort: "name",
page: page,
pageSize: pageSize,
search: query
}).$promise.then(function(result){
return {
results: result,
totalResultCount: result.length
}
});
};
var loadPageCallbackWithDebounce;
$scope.$watch('filterText', function(){
if(loadPageCallbackWithDebounce){
loadPageCallbackWithDebounce();
}
});
vm.getLoadResultsCallback = function(loadPageCallback){
loadPageCallbackWithDebounce = _.debounce(loadPageCallback, 500);
};
// INIT
vm.getMachineModels();
}
])
}());
It's close to the demo except for the item properties and the dataservice is my own ofc.
This is where my ajax-call ends up. I use ASP.NET Web Api 2 as server.
[Route("", Name = "ManufacturerMachineModelList")]
public async Task<IHttpActionResult> Get(int manufacturerId, // Url values
// Filtering (None)
string sort = "name", // Sorting
int page = 1, int pageSize = 5, //Pagination
string search = "") // Search
{
try
{
var manufacturer = Uow.Manufacturer.GetById(manufacturerId);
// Not Found
if (manufacturer == null) { return NotFound(); }
var machineModels = Uow.MachineModel.GetAll(sort, search)
// Url filter
.Where(mm => mm.ManufacturerId == manufacturerId)
// Query filters (None)
.ToList();
// Pagination
var paginationHeader = GetPaginationHeader(machineModels, pageSize, page, sort);
HttpContext.Current.Response.Headers.Add("Pagination", JsonConvert.SerializeObject(paginationHeader));
// Return list and add pagination
return Ok(machineModels
.Skip(pageSize * (page - 1))
.Take(pageSize)
.ToList());
}
catch (Exception exc)
{
return InternalServerError(exc);
}
}
The "GetPaginationHeader()" method returns a anonymous object that looks like this:
var object = new
{
currentPage = page,
pageSize = pageSize,
totalCount = totalCount,
totalPages = totalPages,
previousPageLink = prevLink,
nextPageLink = nextLink
}
Which I then add as a header that's exposed through CORS.
These values are always correct.
The thing is that I'm not really sure what to do with these values, if anything. At the moment I just store the values when the response that includes "Pagination" header comes back and nothing more.
When I make this call I get 11 results back, and the table shows the correct pageSize (5), but not the correct amount of pages (always 1, should be 3), so I only see the first five results.
So, finally, my question is, which values in the ajax search demo should I change to my own values? If none, what else am I doing wrong?
I'm totally stuck so any help is appriciated!
EDIT: I've been trying to get the demo working locally but without luck. If someone can help me get that to work, then I can debug the example and see where I go wrong.
Related
So I ran into this problem two days back and still haven't got a proper solution. I would highly Appreciate any help in here.
Let me explain the scenario first, so the idea is I have one django based ecommerce site and I want to render the product showcase page through ajax call (without reloading) and same time also update the url as the selected filters for example (http://vps.vanijyam.com:8000/customerlist?category=category_1).
I want to achieve similar to this site - shutterstock.
My Scenario -
http://vps.vanijyam.com:8000/customerlist this page to showcase all the available products and also have the filters option and pagination.
Now when I change the page or apply some filter I am e.g. http://vps.vanijyam.com:8000/customerlist?category_slug=category_1 then it is working, but when I refresh the page it is not.. the reason behind this the way I am handling this ajax call in the backend.
def customer_categories(request):
# Get attributes from request url
current_page = request.GET.get('page' ,'1')
category_slug = request.GET.get('category_slug')
sortby_filter = request.GET.get('sortby_filter')
price_filter = request.GET.get('price_filter')
search_query= request.GET.get('term')
line_filter_min = request.GET.get('price_min')
line_filter_max = request.GET.get('price_max')
# Set limit and offset for queryset
limit = REQUEST_PER_PAGE * int(current_page)
offset = limit - REQUEST_PER_PAGE
categories = Category.objects.all()
# products = Product.objects.filter(available=True)[offset:limit] # limiting products based on current_page
# products_count = Product.objects.filter(available=True).count()
# Check product already in cartlist
cartlist_check = []
cart_item_count = cart_count(request)
cart_items = cart_list(request)
for cart in cart_items:
cartlist_check.append(cart['product'].id)
# Check product already in wishlist, only if user logged in
wishlist_check =[]
if request.user.is_authenticated:
wishlist_items_check = WishList.objects.filter(user=request.user)
for item in wishlist_items_check:
wishlist_check.append(item.product_id)
wishlist_count = wishlist_counts(request.user)
else:
wishlist_count = 0
# If category_slug True
if category_slug:
category = get_object_or_404(Category, slug=category_slug).\
get_descendants(include_self=True)
else:
category = None
time1 = time.time()
# Filters for multiselect, retun products and products_count
products, products_count, search_list = attribute_filter(category=category,
search_query=search_query,
sortby_filter=sortby_filter,
price_filter=price_filter,
line_filter_min=line_filter_min,
line_filter_max=line_filter_max,
offset=offset,
limit=limit)
time2= time.time()
print('Time Elapsed')
print(time2-time1)
if len(products) > 0:
# adding one more page if the last page will contains less products
total_pages = math.ceil(products_count / REQUEST_PER_PAGE )
cart_product_form = CartAddProductForm()
wish_list_form = WishListForm()
total_pages = math.ceil(products_count / REQUEST_PER_PAGE ) # adding one more page if the last page will contains less products
if not current_page == '1' or category_slug:
print('------------------------------')
return render(request, 'customer/products/products_by_category.html',\
{'products': products,
'wishlist_item_check': wishlist_check,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
'total_pages':total_pages
})
else:
return render(request, 'customer/home/customer_showcase.html',\
{'products': products,
'categories':categories,
'cart_product_form': cart_product_form,
'wish_list_form': wish_list_form,
'wishlist_item_check': wishlist_check,
'wishlist_count': wishlist_count,
'cart':cart_items,
'items_count':cart_item_count,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_pages':total_pages,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
})
Ajax part of the code is here
$('.selected_subcategory').on('click', function () {
send_data['selected_subcategory'] = $(this).attr('data-id');
getPopularProductsData($(this).attr('data-id'));
// getAPIData();
var params = window.location.search;
var path = window.location.pathname;
var old_url = path + params;
var url = old_url;
const state = {}
const title = ''
console.log('old urll', old_url)
let new_url=''
if(params){
new_url = removeDuplicate(old_url)
}
console.log('new url', new_url)
history.pushState(state, title, url)
$.ajax({
method: 'GET',
url: old_url,
data: {
category_slug: send_data['selected_subcategory']
},
beforeSend: function () {
$('#products').html('<div class="alert alert-success">Loading...</div>');
// $('#spinner3').addClass('d-block');
},
success: function (result) {
if (result['error']) {
let message =
'<div class="alert alert-danger">' +
result['error'] +
' <a class="" href="http://vps.vanijyam.com:8000/customerlist/" style="text-decoration: underline">click here</a>' +
'</div>';
$('#products').html(message);
} else {
document.getElementById('products').innerHTML = result;
}
const state = {}
const title = ''
const url = this.url
history.pushState(state, title, url)
},
error: function (response) {
$('html,body').animate({
scrollTop: 200,
});
$('#products').html(
'<div class="alert alert-danger">Something went wrong!!!</div>'
);
$('#list_data').hide();
// $('#spinner3').addClass('d-none');
},
});
});
My expectation is when I browse this http://vps.vanijyam.com:8000/customerlist?page=2&category_slug=category_1 link it would render the same which matches with the query params, but in a ajax way.
Sorry for the long explanation. Hope my point is clear through this explanation. Thanks in advance
Here in your Django part, you are returning two different HTML outputs when there is category_slug and not.
If there is category_slug in your request you returning 'customer/products/products_by_category.html'
But when there is no category slug you are returning 'customer/home/customer_showcase.html'.
Both HTML files are different in their layout. The first one doesn't provide the header or container elements. This is the central problem of your issue.
You can Fix this issue by
You can put a special parameter in ajax api call, by which Django can realize that is is an api call, this time it returns products_by_category.html.
And you want to make unique all other returns to customer_showcase.html, but if there is a category filter you can pass filtered content to the products list. If category_slug is None or empty you can pass all products without the filter to the same HTML file.
You can also differentiate ajax api call by making it a POST request and all other web requests remains GET requests. So you can easily identify the traffic.
Here is the changes in Django:
if request.method == "POST"::
print('------------------------------')
return render(request, 'customer/products/products_by_category.html',\
{'products': products,
'wishlist_item_check': wishlist_check,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
'total_pages':total_pages
})
else:
return render(request, 'customer/home/customer_showcase.html',\
{'products': products,
'categories':categories,
'cart_product_form': cart_product_form,
'wish_list_form': wish_list_form,
'wishlist_item_check': wishlist_check,
'wishlist_count': wishlist_count,
'cart':cart_items,
'items_count':cart_item_count,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_pages':total_pages,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
})
And make your ajax call to "POST", change in your front end code: method: 'POST',
Don't forget to add slash(/) at the end of url when you change to POST.
I have a Kendo.MVC project. The view has a model with a field of type List<>. I want to populate the List from a Javascript function. I've tried several ways, but can't get it working. Can someone explain what I'm doing wrong?
So here is my model:
public class Dashboard
{
public List<Note> ListNotes { get; set; }
}
I use the ListNotes on the view like this:
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
}
This works if I populate Model.ListNotes in the controller when the view starts...
public ActionResult DashBoard(string xsr, string vst)
{
var notes = rep.GetNotesByCompanyID(user.ResID, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
Dashboard employee = new Dashboard
{
ResID = intUser,
Type = intType,
FirstName = user.FirstName,
LastName = user.LastName,
ListNotes = listNotes
};
return View(employee);
}
... but I need to populate ListNotes in a Javascript after a user action.
Here is my javascript to make an ajax call to populate ListNotes:
function getReminders(e)
{
var userID = '#ViewBag.CurrUser';
$.ajax({
url: "/api/WoApi/GetReminders/" + userID,
dataType: "json",
type: "GET",
success: function (notes)
{
// Need to assign notes to Model.ListNotes here
}
});
}
Here's the method it calls with the ajax call. I've confirmed ListNotes does have the values I want; it is not empty.
public List<Koorsen.Models.Note> GetReminders(int id)
{
var notes = rep.GetNotesByCompanyID(id, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
foreach (Koorsen.OpenAccess.Note note in notes)
{
Koorsen.Models.Note newNote = new Koorsen.Models.Note()
{
NoteID = note.NoteID,
CompanyID = note.CompanyID,
LocationID = note.LocationID,
NoteText = note.NoteText,
NoteType = note.NoteType,
InternalNote = note.InternalNote,
NoteDate = note.NoteDate,
Active = note.Active,
AddBy = note.AddBy,
AddDate = note.AddDate,
ModBy = note.ModBy,
ModDate = note.ModDate
};
listNotes.Add(newNote);
}
return listNotes;
}
If ListNotes was a string, I would have added a hidden field and populated it in Javascript. But that didn't work for ListNotes. I didn't get an error, but the text on the screen didn't change.
#Html.HiddenFor(x => x.ListNotes)
...
...
$("#ListNotes").val(notes);
I also tried
#Model.ListNotes = notes; // This threw an unterminated template literal error
document.getElementById('ListNotes').value = notes;
I've even tried refreshing the page after assigning the value:
window.location.reload();
and refreshing the panel bar the code is in
var panelBar = $("#IntroPanelBar").data("kendoPanelBar");
panelBar.reload();
Can someone explain how to get this to work?
I don't know if this will cloud the issue, but the reason I need to populate the model in javascript with an ajax call is because Model.ListNotes is being used in a Kendo Panel Bar control and I don't want Model.ListNotes to have a value until the user expands the panel bar.
Here's the code for the panel bar:
#{
#(Html.Kendo().PanelBar().Name("IntroPanelBar")
.Items(items =>
{
items
.Add()
.Text("View Important Notes and Messages")
.Expanded(false)
.Content(
#<text>
#RenderReminders()
</text>
);
}
)
.Events(e => e
.Expand("getReminders")
)
)
}
Here's the helper than renders the contents:
#helper RenderReminders()
{
if (Model.ListNotes.Count <= 0)
{
#Html.Raw("No Current Messages");
}
else
{
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
<br />
}
}
}
The panel bar and the helpers work fine if I populate Model.ListNotes in the controller and pass Model to the view. I just can't get it to populate in the javascript after the user expands the panel bar.
Perhaps this will do it for you. I will provide a small working example I believe you can easily extend to meet your needs. I would recommend writing the html by hand instead of using the helper methods such as #html.raw since #html.raw is just a tool to generate html in the end anyways. You can write html manually accomplish what the helper methods do anyway and I think it will be easier for you in this situation. If you write the html correctly it should bind to the model correctly (which means it won't be empty on your post request model) So if you modify that html using javascript correctly, it will bind to your model correctly as well.
Take a look at some of these examples to get a better idea of what I am talking about:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
So to answer your question...
You could build a hidden container to hold your list values like this (make sure this container is inside the form):
<div id="ListValues" style="display:none">
</div>
Then put the results your ajax post into a javascript variable (not shown).
Then in javascript do something like this:
$('form').off('submit'); //i do this to prevent duplicate bindings depending on how this page may be rendered futuristically as a safety precaution.
$('form').on('submit', function (e) { //on submit, modify the form data to include the information you want inside of your ListNotes
var data = getAjaxResults(); //data represents your ajax results. You can acquire and format that how you'd like I will use the following as an example format for how you could save the results as JSON data: [{NoteID ="1",CompanyID ="2"}]
let listLength = data.length;
for (let i = 0; i < listLength; i++) {
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].NoteID " value="' + data.NoteID +'" />')
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].CompanyID " value="' + data.CompanyID +'" />')
//for your ajax results, do this for each field on the note object
}
})
That should do it! After you submit your form, it should automatically model bind to you ListNotes! You will be able to inpsect this in your debugger on your post controller action.
I'm currently working on an AngularJS project and I got stuck in this specific requirement.
We have a service that has all the data, DataFactoryService. Then, I have a controller called DataFactoryController that is making the magic and then plot it in the view.
<div ng-repeat = "list in collection">
{{list.name}}
...
</div>
Now, we have a requirement that pass multiple data into one element. I thought an "ng-repeat" would do, but we need to have it inside an element attribute.
The scenarios are:
At one of the pages, we have multiple lists with multiple data.
Each data has a unique code or ID that should be passed when we do an execution or button click.
There are instances that we're passing multiple data.
Something like this (if we have 3 items in a list or lists, so we're passing the 3 item codes of the list):
<a href = "#" class = "btn btn-primary" data-factory = "code1;code2;code3;">
Submit
</a>
<a href = "#" class = "btn btn-default" data-factory = "code1;code2;code3;">
Cancel
</a>
In the example above, code1,code2,code3 came from the list data. I tried several approach like "ng-repeat", "angular.each", array, "ng-model" but I got no success.
From all I've tried, I knew that "ng-model" is the most possible way to resolve my problem but I didn't know where to start. the code below didn't work though.
<span ng-model = "dataFactorySet.code">{{list.code}}</span>
{{dataFactorySet.code}}
The data is coming from the service, then being called in the controller, and being plot on the HTML page.
// Controller
$scope.list = dataFactoryService.getAllServices();
The data on the list are being loaded upon initialization and hoping to have the data tags initialized as well together with the list data.
The unique code(s) is/are part of the $scope.list.
// Sample JSON structure
[
{ // list level
name: 'My Docs',
debug: false,
contents: [ // list contents level
{
code: 'AHDV3128',
text: 'Directory of documents',
...
},
{
code: 'AHDV3155',
text: 'Directory of pictures',
...
},
],
....
},
{ // list level
name: 'My Features',
debug: false,
contents: [ // list contents level
{
code: 'AHGE5161',
text: 'Directory of documents',
...
},
{
code: 'AHGE1727',
text: 'Directory of pictures',
...
},
],
....
}
]
How can I do this?
PLUNKER -> http://plnkr.co/edit/Hb6bNi7hHbcFa9RtoaMU?p=preview
The solution for this particular problem could be writing 2 functions which will return the baseId and code with respect to the list in loop.
I would suggest to do it like below
Submit
Cancel
//inside your controller write the methods -
$scope.getDataFactory = function(list){
var factory = list.map( (a) => a.code );
factory = factory.join(";");
return factory;
}
$scope.getDataBase= function(list){
var base= list.map( (a) => a.baseId);
base= base.join(";");
return base;
}
Let me know if you see any issue in doing this. This will definitely solve your problem.
You don't really have to pass multiple data from UI if you are using Angular.
Two-way data binding is like blessing which is provided by Angular.
check your updated plunker here [http://plnkr.co/edit/mTzAIiMmiVzQfSkHGgoU?p=preview]1
What I have done here :
I assumed that there must be some unique id (I added Id in the list) in the list.
Pass that Id on click (ng-click) of Submit button.
You already have list in your controller and got the Id which item has been clicked, so you can easily fetch all the data of that Id from the list.
Hope this will help you... cheers.
So basing from Ashvin777's post. I came up with this solution in the Controller.
$scope.getFactoryData = function(list) {
var listData = list.contents;
listData = listData.map(function(i,j) {
return i.code;
});
return listData.join(';');
}
Working on an ngTable that has more than 1000 records.
Everything works fine, but the only problem I am facing is the filter.
it only filters text in the active page.
Lets say I am in page 3, and if I search/filter something using a textbox it has to be from page 3 otherwise it will show nothing..
my code is as follow:
$scope.$watch("filter.$", function () {
$scope.tableParams.reload();});
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
schoolName: 'asc' // initial sorting
}
},
{
total: $scope.schoolInfo.length, // length of data
getData: function ($defer, params) {
var filteredData = {};
//reason I am doing this is to get the data always filtered, no matter what
var orderedData = $filter('orderBy')($scope.schoolName, params.orderBy());
params.total(orderedData); // set total for recalc pagination
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
},$scope:$scope
});
the sorting works perfect, but its the search only
Search: <input type="text" ng-model="search.schoolName">
I also tried ng-model="search"aswell
<tr ng-repeat="mydata in $data | filter:search">
I also tried search:strict
nothing seems to help if someone can please help me?
Thanks in advance
Take a look at the following example from the ng-table: Table combining sorting and filtering
Try reworking your code to use the code from this example.
In my template I have this code:
<li class="row timeline-item" data-ng-repeat="item in data | filter:workoutFilter" data-ng-include="getTemplateUrl(item)"></li>
And in my controller I have:
$scope.workoutFilter = null;
$rootScope.$on('workout', function(e, data) {
$scope.workoutFilter = "{Type: 'workout'}";
});
When I click button workouts, everything just disappears as if there are no items that match this filter, but that isn't the case (and I know this for a fact because getTemplateUrl detects type workout and returns right template)
Why is this happening ? Is there any other way I can filter items which have property Type "workout" ?
EDIT
Here is what happens when I click button:
In my other controller :
$scope.onWorkoutsClick = function () {
feedService.setWorkoutFilter();
};
In service:
setWorkoutFilter : function() {
$rootScope.$broadcast('workout');
}
And in my main controller:
$rootScope.$on('workout', function(e, data) {
$scope.workoutFilter = "{Type: 'workout'}";
});
I assume you're trying to filter based on item.Type == 'workout'?
You need to set your filter to an object rather than a string:
$scope.workoutFilter = {Type: 'workout'};