User search refactored, autocomplete added. bootstrap3-typeahead jQuery plugin added.

This commit is contained in:
Alexey Skobkin 2016-12-11 01:48:51 +03:00
parent d71ea8a116
commit d0c103eae0
8 changed files with 162 additions and 30 deletions

View File

@ -35,6 +35,9 @@ sensio_framework_extra:
# Twig Configuration
twig:
form:
resources:
- bootstrap_3_layout.html.twig
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
@ -82,4 +85,4 @@ swiftmailer:
knp_markdown:
parser:
service: markdown.parser.point
service: markdown.parser.point

View File

@ -3,17 +3,42 @@
namespace Skobkin\Bundle\PointToolsBundle\Controller;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\QueryBuilder;
use Skobkin\Bundle\PointToolsBundle\Form\UserSearchType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class MainController extends Controller
{
public function indexAction()
public function indexAction(Request $request)
{
/** @var EntityManager $em */
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(
new UserSearchType(),
null,
[
'action' => $this->generateUrl('index'),
'method' => 'POST',
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$login = $form->get('login')->getData();
if (null !== $user = $em->getRepository('SkobkinPointToolsBundle:User')->findOneBy(['login' => $login])) {
return $this->redirectToRoute('user_show', ['login' => $login]);
}
$form->get('login')->addError(new FormError('Login not found'));
}
return $this->render('SkobkinPointToolsBundle:Main:index.html.twig', [
'form' => $form->createView(),
'users_count' => $em->getRepository('SkobkinPointToolsBundle:User')->getUsersCount(),
'subscribers_count' => $em->getRepository('SkobkinPointToolsBundle:Subscription')->getUserSubscribersCountById($this->container->getParameter('point_id')),
'events_count' => $em->getRepository('SkobkinPointToolsBundle:SubscriptionEvent')->getLastDayEventsCount(),
@ -21,4 +46,20 @@ class MainController extends Controller
'last_events' => $em->getRepository('SkobkinPointToolsBundle:SubscriptionEvent')->getLastSubscriptionEvents(10),
]);
}
public function searchUserAjaxAction($login)
{
$em = $this->getDoctrine()->getManager();
$result = [];
foreach ($em->getRepository('SkobkinPointToolsBundle:User')->findUsersLikeLogin($login) as $user) {
$result[] = [
'login' => $user->getLogin(),
'name' => $user->getName(),
];
}
return new JsonResponse($result);
}
}

View File

@ -50,19 +50,6 @@ class UserController extends Controller
]);
}
/**
* @param Request $request
*/
public function searchUserAction(Request $request)
{
$login = $request->request->get('login');
if (!$login) {
return $this->redirectToRoute('index');
}
return $this->redirectToRoute('user_show', ['login' => $login]);
}
/**
* @param TopUserDTO[] $topUsers
* @return Highchart

View File

@ -0,0 +1,39 @@
<?php
namespace Skobkin\Bundle\PointToolsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserSearchType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('login')
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'Skobkin\Bundle\PointToolsBundle\Entity\User',
]);
}
/**
* @return string
*/
public function getName()
{
return 'skobkin_bundle_pointtoolsbundle_user_search';
}
}

View File

@ -12,7 +12,7 @@ class UserRepository extends EntityRepository
* Case-insensitive user search
*
* @param string $login
* @return User[]
* @return User|null
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function findUserByLogin($login)
@ -28,6 +28,31 @@ class UserRepository extends EntityRepository
;
}
/**
* Case insensitive user LIKE %login% search
*
* @param string $login
*
* @return User[]
*/
public function findUsersLikeLogin($login)
{
if (empty($login)) {
return [];
}
$qb = $this->createQueryBuilder('u');
return $qb
->where('u.login LIKE :login')
->orderBy('u.login', 'ASC')
->setMaxResults(10)
->setParameter('login', '%'.strtolower($login).'%')
->getQuery()
->getResult()
;
}
/**
* @return integer
*/

View File

@ -2,10 +2,12 @@ index:
path: /
defaults: { _controller: SkobkinPointToolsBundle:Main:index }
user_search:
path: /user/search
defaults: { _controller: SkobkinPointToolsBundle:User:searchUser }
methods: [POST]
user_search_ajax:
path: /ajax/users/search/{login}
defaults: { _controller: SkobkinPointToolsBundle:Main:searchUserAjax, _format: json }
requirements:
login: "[\w-]*"
_format: json
user_show:
path: /user/{login}

View File

@ -1,18 +1,52 @@
{% extends "::base.html.twig" %}
{% block head_js %}
{{ parent() }}
<script src="{{ asset('js/bootstrap3-typeahead.min.js') }}"></script>
{% endblock %}
{% block content %}
<div class="well well-lg">
{# @todo rewrite to Symfony forms #}
<form class="form-inline" method="post" action="{{ path('user_search') }}">
{{ form_start(form, {'attr': {'class': 'form-inline'} }) }}
<div class="form-group">
<label class="sr-only" for="index-input-username">{{ 'Username'|trans }}</label>
<div class="input-group">
<div class="input-group-addon">@</div>
<input name="login" type="text" class="form-control" id="index-input-username" placeholder="username">
</div>
{{ form_errors(form.login) }}
{{ form_widget(form.login, {'attr': {'autocomplete': 'off'}}) }}
<script type="text/javascript">
$(function() {
$field = $('#{{ form.login.vars.id }}');
$field.typeahead({
minLength: 2,
delay: 500,
autoSelect: true,
source: function (query, processCallback) {
$.get('{{ path('user_search_ajax', {'login': ''}) }}' + query, function (data) {
processCallback(data);
});
},
afterSelect: function () {
$field.parents('form').first().submit();
},
displayText: function (item) {
// Crutches to place only login into the field after selecting the item
if (typeof item === 'object') {
return item.login+(item.name ? ' ('+item.name+')' : '');
} else if (typeof item === 'string') {
return item;
}
},
updater: function (item) {
// Crutches to place only login into the field after selecting the item
return item.login;
}
});
});
</script>
<input type="submit" value="{{ 'Search'|trans }}" class="btn btn-default" />
</div>
<button type="submit" class="btn btn-primary">{{ 'Search'|trans }}</button>
</form>
{{ form_end(form) }}
</div>
<div class="container service-stats">

1
web/js/bootstrap3-typeahead.min.js vendored Normal file

File diff suppressed because one or more lines are too long