Filter card list with Razor View - angularjs

Im trying to make a filter like in AngularJS when u use:
ng-repeat="u in users | filter:searchBar">
And your input filter looks like
<input type="text" id="searchBar" placeholder="start typing" ng-model="searchBar">
But the things its that im working on MVC with Razor View and I do not know how to approach this filter.
The list of cards is made with a foreachlike this:
#foreach{ var item in Models){
<div class="card">
<div class="card-container">
some content
</div>
</div>
}
Any ideas?

You can do the filtering with ajax. Here is a server side filtering solution.
First, you should move the code which renders the result to a partial view. Let's say you created a partial view called CustomerList.cshtml. Move the list code to that.
#model IEnumerable<Customer>
#foreach (var item in Model)
{
<div class="card">
<div class="card-container">
#item.Name
</div>
</div>
}
Now in your main view, you can call this partial view and pass the data to it. Wrap the call to the partial view in a container div. Add a input element for user to enter the search key. Assuming your main view is also strongly typed to IEnumerable<Customer>
#model IEnumerable<Customer>
<input type="text" id="search" data-url="#Url.Action("Index")" />
<div id="div-items">
#Html.Partial("CustomerList",Model)
</div>
Now we need to have some javascript code which listen to the keyup event on the search input, read the value of it and make an ajax call to the server where it uses the search key and get the filtered set of data, pass that to the same partial view and return the partial view result.
You can use jQuery $.get method
$(document).ready(function () {
$("#search").keyup(function() {
var v = $(this).val();
$.get($(this).data("url"), { searchKey: v }).done(function(res) {
$("#div-items").html(res);
});
});
});
Now make sure your server action method returns the filtered data like this
public ActionResult Index(string serchKey="")
{
var items = db.Customers.AsQueryable();
if (!String.IsNullOrEmpty(searchKey))
{
items = items.Where(a => a.Name.StartsWith(searchKey));
}
var t = items.ToList();
if (Request.IsAjaxRequest())
{
return PartialView("CustomerList",t );
}
return View(t);
}
Another option is to do client side filtering. on the items. But if i am going that direction, i would choose a client side MVC framework like angular to do that for me

Related

ngRepeat doesn't refresh rendered value

I'm having an issue with ngRepeat :
I want to display a list of students in two different ways. In the first one they are filtered by group, and in the second they are not filtered.
The whole display being quite complex, I use a ngInclude with a template to display each student. I can switch between view by changing bClasseVue, each switch being followed by a $scope.$apply().
<div ng-if="currentCours.classesOfGroup !== undefined"
ng-show="bClassesVue">
<div ng-repeat="group in currentCours.classesOfGroup">
<br>
<h2>Classe : [[group.name]]</h2>
<div class="list-view">
<div class="twelve cell"
ng-repeat="eleve in group.eleves | orderBy:'lastName'"
ng-include="'liste_eleves.html'">
</div>
</div>
</div>
</div>
<div class="list-view" ng-show="!bClassesVue">
<div class="twelve cell"
ng-repeat="eleve in currentCours.eleves.all"
ng-include="'liste_eleves.html'">
</div>
</div>
My problem happens when my list of students change (currentCours here). Instead of refreshing the ngRepeat, both lists concatenate, but only in the unfiltered view.
I tried adding some $scope.$apply in strategic places (and I synchronize my list for example) but it doesn't help.
EDIT : the function used to refresh currentCours in the controller. It's called when a "cours" is selected inside a menu.
$scope.selectCours = function (cours) {
$scope.bClassesVue = false;
$scope.currentCours = cours;
$scope.currentCours.eleves.sync().then(() => {
if ($scope.currentCours.classe.type_groupe === 1) {
let _elevesByGroup = _.groupBy($scope.currentCours.eleves.all, function (oEleve) {
return oEleve.className;
});
$scope.currentCours.classesOfGroup = [];
for(let group in _elevesByGroup) {
$scope.currentCours.classesOfGroup.push({
name: group,
eleves: _elevesByGroup[group]
});
}
$scope.bClassesVue = true;
}
});
utils.safeApply($scope);
};
Well, I found a workaround, but I still don't know why it didn't work, so if someone could write an explanation, I would be very thankful.
My solution was simply to open and close the template each time I switch between views.

Grails GSP Loop through an index and do somthing with selected lines

In an Index-gsp, I want to be able to select an arbitrary number of lines and then by clicking a link send all those lines to a controller for processing e.g. creating new objects of a different kind.
I've no idea how selection can be done or how to collect these selected lines in a GSP. Maybe I should use a checkbox on each line if that's possible?
It's a list of products which is displayed using a modified index.gsp.
Each product-line has a checkbox in front.
What I want is to make a list of the products that are checked an then transmit this list to a controller.
a part of this index.gsp:
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><g:link class="create" action="createOffer"><g:message code="default.new.label" args="[entityName]" params="toOffer" /></g:link></li>
</ul>
</div>
<div id="list-prodBuffer" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<table>
<thead>
<tr>
<td> Välj</td>
<td> ID</td>
</tr>
</thead>
<tbody>
<g:each in="${prodBufferList}" status="i" var="prodBuffer">
<tr class="${ (i % 2) == 0 ? 'even': 'odd'}">
<td><g:checkBox name="toOffer" value="${prodBuffer.id}" checked="false" /></td>
<td>${prodBuffer.id}</td>
So this not an ordinary form, just a list where I want to use a link to transmit it to the controller.
I'm a beginner and have no idea how to do it.
You can collect all necessary data from page using javascript, and then send all data to your controller for processing.
There are a lot of ways to do it.
For example send via JQuery:
<script>
//some code
var items = [1,2,3];
//some code
$('#add-location').click(function () {
$.ajax({
type: "POST",
url: "${g.createLink(controller:'myController', action: 'myControllerMethod')}",
data: {items: items},
success: function (data) {
console.log(data)
}
});
});
</script>
I will answer this but have to slow down since it feels like i am beginning to write your project:
In gsp you will need to have a hidden field followed by a check box amongst data you are trying to capture, checkbox should contain all the data elements required to build your output.
<g:hiddenField name="userSelection" value=""/>
<g:checkBox name="myCheckBox" id='myCheckBox' value="${instance.id}"
data-field1="${instance.field1}" data-field1="${instance.field1}"
checked="${instance.userSelected?.contains(instance.id)?true:false}" />
In the java script segment of the page you will need to add the following
This will then auto select selection and add to javascript array
// Customized collection of elements used by both selection and search form
$.fn.serializeObject = function() {
if ($("[name='myCheckBox']:checked").size()>0) {
var data=[]
$("[name='myCheckBox']:checked").each(function() {
var field1=$(this).data('field1');
var field2=$(this).data('field2');
data.push({id: this.value, field1:field1, field2:field2 });
});
return data
}
};
Most importantly will your data sit across many different gsp listing pages if so you will need to hack pagination:
//Modify pagination now to capture
$(".pagination a").click(function() {
var currentUrl=$(this).attr('href');
var parsedUrl=$(this).attr('href', currentUrl.replace(/\&userSelection=.*&/, '&').replace(/\&userSelection=\&/, '&'));
var newUrl=parsedUrl.attr('href') + '&userSelection=' + encodeURIComponent($('#userSelection').val());
window.location.href=newUrl
return false;
});
Then in the controller parse the JSON form field and make it into what you want when posted
def u=[]
def m=[:]
if (params.userSelection) {
def item=JSON.parse(params.userSelection)
item?.each {JSONObject i->
// When field1 is null in JSON set it as null properly
if (JSONObject.NULL.equals(i.field1)) {
i.field1=null
}
if (resultsGroup) {
if (!resultsGroup.contains(i.id as Long)) {
u << i
}
} else {
u << i
}
}
m.userSelected=item?.collect{it.id as Long}
m.results=u
}
return m

Why does my <a> taghelper route go dead after its route is used by a form post?

This is a ASP.Net Core MVC project.
In my layout file I have the following Menu link:
<li><a asp-controller="Employees" asp-action="Index">Employees</a></li>
The MVC controller it routes to looks like this:
public IActionResult Index()
{
return View();
}
When I click the link the Action is hit and the view is rendered with my Employee List View.
The Employee List View is bound to an Angular Controller which calls a corresponding Employees Web API GET and the view shows my employee list unfiltered.
Great.
Now we need an Employee quick search from a quick search panel.
So I modify my MVC Employees controller like this:
public IActionResult Index(EmployeeListPageViewModel empListPageVM)
{
return View(empListPageVM);
}
It takes in this Model:
public class EmployeeListPageViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
My Quick Search Form looks like this back in the layout file:
<form asp-controller="Employees" asp-action="Index">
<th>Employee Search:</th>
<td><input id="firstName" name="firstName" type="text" placeholder="FirstName" /></td>
<td><input id="lastName" name="lastName" type="text" placeholder="LastName" /></td>
<td>
<button class="btn btn-xs btn-primary glyphicon glyphicon-search">
</button>
</td>
</form>
Now the model is built from the form and sent to my MVC Employees Index action.
And of course I roll all the needed changes through.
Make my Employees Web API controller take in optional params.
FirstName = null, LastName = null.
The employee list view takes in the ViewModel:
#model EmployeeListPageViewModel
Binds to the Angular Controller:
ng-controller="employeesController
Calls getEmployees:
ng-init="getEmployees('#Model.FirstName', '#Model.LastName')
The Angular controller works out whether everything is null or filtering is needed:
/***** List Employees *****/
$scope.getEmployees = function (pfirstName, pLastName) {
var config = {
params: {
firstName: pfirstName,
lastName: pLastName
}
}
$http.get(employeeUrl, config)
.then(function (response) {
// Test front end exception message;
// throw "test exception";
$scope.data.employees = response.data;
})
.catch(function (error) {
$scope.data.employeeListError = error;
});
}
Hope all of this makes sense. Just laying the foundation here.
Now my problem:
Everything seems to work individually.
But, when I go in fresh and click the Employees Menu Link I get my full list.
And when I fill in FirstName And/or LastName in the quick search it works.
But now the Employees menu link is dead. It doesn't fire. It doesn't hit the Employees Index Controller action.
What is it about the form that is killing the Menu Link?
Update 1: After thinking about this I believe the anchor tag helper is looking at the controller and index and saying, "I am already there." So it is not going to the controller action. How do I force it to go even if it is already there?
Update 2: I tried changing the link to this:
<li>Employees</li>
The link works but it is still killed after the form post.
Apparently, no matter how you direct to the link, taghelper, ng-href, straight link, whatever, if you are already there the link will not go.
I had to replace the anchor link in the menu with this:
<li>
<form asp-controller="Employees" asp-action="Index">
<button type="submit" class="navbarLinks">
Employees
</button>
</form>

MVC refuses a connection to only one action

Below is part of the Angular controller I use to populate three cascading lists of address data, with the last being a BootStrap Tab Page widget. I have only tested on Chrome and Edge so far, with similar results.
public class AreaController : BaseController
{
private readonly AreaClient _areaClient = new AreaClient(UserHelper.CurrentUser);
private readonly AgentClient _agentClient = new AgentClient();
[HttpPost]
public JsonResult ProvincesJson()
{
return Json(_areaClient.GetProvinces().ToList());
}
[HttpPost]
public JsonResult AreasJson(int provinceId)
{
var model = _areaClient.GetAreas(provinceId).ToList();
return Json(model);
}
[HttpGet]
public JsonResult SuburbsJson(int agentId, int areaId)
{
var allBurbs = _areaClient.GetSuburbs(areaId).ToList();
var agentBurbIds = _agentClient.GetAgentSuburbs(agentId).Select(ab => ab.SuburbId).ToList();
var model = allBurbs.Select(burb => new CheckListItemModel { Id = burb.SuburbId, Label = burb.SuburbName, IsChecked = agentBurbIds.Contains(burb.SuburbId) }).ToList();
return Json(model);
}
}
ProvincesJson and AreasJson work perfectly for this partial view:
<div id="areas-and-suburbs" ng-controller="areasCtrl">
<div class="form-group">
<select id="ProvinceId" ng-model="geo.provinces.selectedId" ng-change="geo.getAreas(agentId, geo.provinces.selectedId)" class="form-control">
<option ng-repeat="item in geo.provinces" ng-value="{{item.provinceId}}" class=".area-option">{{item.provinceName}}</option>
</select>
</div>
<div class="form-group">
<select id="AreaId" ng-model="geo.areas.selectedId" ng-change="geo.setSuburbs(geo.areas.selectedId)" class="form-control">
<option ng-repeat="item in geo.areas" ng-value="{{item.areaId}}" class=".area-option">{{item.areaName}}</option>
</select>
</div>
<div class="form-group">
<div id="area-suburbs-partial">
#Html.Partial("_Suburbs")
</div>
</div>
</div>
The inner partial, _Suburbs looks like this:
$scope.geo.getSuburbs = function(agentId, areaId) {
var geoUrl = "\/Area/SuburbsJson";
$http.post(geoUrl, { areaId: 3, agentId: 1 }, postOptions)
.then(function(response) {
var model = angular.fromJson(response.data);
$scope.agentSuburbs = model.$values;
_.defer(function() {
$scope.$apply();
});
},
function error(response) {
alert("Ajax error [getSuburbs]: " + response.responseText);
});
};
Yet when the outer parial renders __Suburbs, which calls geo.setSuburbs, I get "localhost refused to connect" error in Chrome. Everything in this project is same domain, just one project, and the Provinces and Areas dropdowns cascade properly, but when I select a new Area, to trigger fetching suburbs for that area, I get the error.
I see very little difference between the three actions, so I really don't understand why a connection to the third is refused. I even removed the business logic from SuburbsJson to return a simple array of int, and called it directly from the browser, vs. from my Angular controller's Ajax logic, and I still got a refused connection.
What could be behind just this one controller action causing a refused connection?
BREAKING:
I was a touch dyslexic somewhere with the spelling of area. Fixing that solved everything on that day.
For one, you're POSTing to your GET method, so MVC won't route it for you.
HTTPGET Methods in MVC have to have the JsonRequestBehavior.AllowGet flag as the second parameter of the return Json.
Instead
// Do other stuff to get model
return Json(model, JsonRequestBehavior.AllowGet);
See this other SO post for why AllowGet is necessary

How to get a single item from a GoInstant collection?

How do you get a single item from a GoInstant GoAngular collection? I am trying to create a typical show or edit screen for a single task, but I cannot get any of the task's data to appear.
Here is my AngularJS controller:
.controller('TaskCtrl', function($scope, $stateParams, $goKey) {
$scope.tasks = $goKey('tasks').$sync();
$scope.tasks.$on('ready', function() {
$scope.task = $scope.tasks.$key($stateParams.taskId);
//$scope.task = $scope.tasks.$key('id-146b1c09a84-000-0'); //I tried this too
});
});
And here is the corresponding AngularJS template:
<div class="card">
<ul class="table-view">
<li class="table-view-cell"><h4>{{ task.name }}</h4></li>
</ul>
</div>
Nothing is rendered with {{ task.name }} or by referencing any of the task's properties. Any help will be greatly appreciated.
You might handle these tasks: (a) retrieving a single item from a collection, and (b) responding to a users direction to change application state differently.
Keep in mind, that a GoAngular model (returned by $sync()) is an object, which in the case of a collection of todos might look something like this:
{
"id-146ce1c6c9e-000-0": { "description": "Destroy the Death Start" },
"id-146ce1c6c9e-000-0": { "description": "Defeat the Emperor" }
}
It will of course, have a number of methods too, those can be easily stripped using the $omit method.
If we wanted to retrieve a single item from a collection that had already been synced, we might do it like this (plunkr):
$scope.todos.$sync();
$scope.todos.$on('ready', function() {
var firstKey = (function (obj) {
for (var firstKey in obj) return firstKey;
})($scope.todos.$omit());
$scope.firstTodo = $scope.todos[firstKey].description;
});
In this example, we synchronize the collection, and once it's ready retrieve the key for the first item in the collection, and assign a reference to that item to $scope.firstTodo.
If we are responding to a users input, we'll need the ID to be passed from the view based on a user's interaction, back to the controller. First we'll update our view:
<li ng-repeat="(id, todo) in todos">
{{ todo.description }}
</li>
Now we know which todo the user want's us to modify, we describe that behavior in our controller:
$scope.todos.$sync();
$scope.whichTask = function(todoId) {
console.log('this one:', $scope.todos[todoId]);
// Remove for fun
$scope.todos.$key(todoId).$remove();
}
Here's a working example: plunkr. Hope this helps :)

Resources