Ответы пользователя по тегу Thymeleaf
  • Themeleaf: Как вывести на страницу сообщение по дефолту?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    Почему бы вам не восопльзоваться возможностями шаблонизатора?
    Вот, несколько вариантов решения вопроса:

    1) Использование тернарного оператора
    <span th:text="${address.city != null} ? ${address.city} : 'No data!'">City</span>


    2) Использование: if unless (аналог if else на java). Обратите внимание, что если city это строка, то можно использовать ==, а если объект, то нужно использовать eq
    <span th:if="${address.city} == null">Non data</span>
    <span th:unless="${address.city} != null" th:text="'Your city : ' + ${address.city}">Non data</span>


    Также обратите внимание, что вы можете "обеспечить защиту" приложение, если значение null используя оператор безопасной навигации ?. между address & city
    <span th:text="'Your city : ' + ${address?.city}">Non data</span>
    Ответ написан
    1 комментарий
  • Как показать уведомление с анимацией без перезагрузки встраницы после сохранения формы в Spring Boot?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    Вы все правильно предположили насчет Ajax & JS + CSS.
    Писать код с нуля не буду, а лишь расскажу вам этапы для достижения желаемого...
    1) Создайте метод, который на ajax запрос будет возвращать @ResponseBody, либо создайте контроллер и добавьте аннотацию @RestController
    2) Перед закрывающим тегом </body> добавьте js код для аякс.
    https://ruseller.com/jquery.php?id=11
    https://api.jquery.com/jquery.ajax/
    3) Далее в контроллере обрабатываете запрос и возвращаете String или например, Map<String, String> с нужными данными.
    4) В ajax на событие success показываете ваш блок с анимацией.

    Теперь, про саму анимацию...
    Анимацию можно сделать средствами CSS или JS.
    Вот, пример CSS
    Допустим вот, ваш блок и он по умолчанию скрыт
    <div class="notify" style="display:none;"></div>
    При success делаете этот блок show() (видимым).
    Показать - https://api.jquery.com/show/
    Скрыть - https://api.jquery.com/hide/

    Остается только добавить анимацию - @keyframes
    https://www.w3schools.com/css/css3_animations.asp

    Вот, готовые библиотеки -
    https://stephanwagner.me/jBox
    https://notifyjs.jpillora.com/
    Ответ написан
    1 комментарий
  • Почему изображения отображаются только после перезагрузки Intelij Idea?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    Вам не нужно перезапускать саму программу.
    Поступите следующим образом:

    После того, как добавите новую статику или внесете изменения в ваш проект, то для того, чтобы увидеть изменения сделайте Build project (ctrl + F9) или Rebuild Project.

    Чтобы самому не заморачиваться каждый раз нажимая на кнопку build / rebuild включите авто сборку.
    Для этого следуйте указаниям:
    1)
    SHIFT+Ctrl+A ->registry-> compiler.automake.allow.when.app.running

    2)
    file->settings->build,execution,deployment. Go to ->compiler->build project automatically.

    3) Добавьте расширение для браузера LiveReload.
    Тогда после внесения правок каждый раз будет происходить авто сборка
    https://stackoverflow.com/questions/33869606/intel...
    Ответ написан
  • Как сделать логин для админа в Spring Security?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    Я реализовал это следующим образом:
    В данном случае у меня 2 кастомные формы входа:
    1) для админ панели /admin/login
    2) для пользователей /auth
    /**
     * Конфигурация для Spring Security
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig {
    
        private static UserDetailsServiceImpl userDetailsService;
    
        @Autowired
        private UserDetailsServiceImpl userDetailsServiceImpl;
    
        @PostConstruct
        private void init() {
            userDetailsService = this.userDetailsServiceImpl;
        }
    
        @Bean
        public static PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public DaoAuthenticationProvider authProvider() {
            CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider();
            authProvider.setUserDetailsService(userDetailsService);
            authProvider.setPasswordEncoder(passwordEncoder());
            return authProvider;
        }
    
        // Конфигурация для backend
        @Configuration
        @Order(1)
        public static class BackendConfigurationAdapter extends WebSecurityConfigurerAdapter {
    
    
            @Autowired
            private CustomWebAuthenticationDetailsSource authenticationDetailsSource;
    
            public BackendConfigurationAdapter() {
                super();
            }
    
            @Override
            protected void configure(HttpSecurity http) throws Exception {
    
                http
                        .antMatcher("/admin/**")
                        .antMatcher("/admin/**/**")
                        .authorizeRequests()
                        .anyRequest()
                        .hasAuthority("ADMIN_PRIVILEGE")
                        /*.hasAuthority("ADMIN")*/
    
    
                        .and()
                            .formLogin()
                            .authenticationDetailsSource(authenticationDetailsSource)
                            .loginPage("/admin/login")
                            .loginProcessingUrl("/admin/login")
                            .usernameParameter("email")
                            .passwordParameter("password")
                            .defaultSuccessUrl("/admin/dashboard")
                            .failureUrl("/admin/login?authError")
                            .permitAll()
    
                        .and()
                            .rememberMe()
                            .rememberMeParameter("remember-me")
                            .tokenValiditySeconds(86400)
    
                        .and()
                            .logout()
                            .logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
                            .logoutSuccessUrl("/admin/login")
                            .deleteCookies("JSESSIONID")
    
                        .and()
                            .exceptionHandling()
                            .accessDeniedPage("/403")
    
                        .and()
                            .csrf()
                            .ignoringAntMatchers("/admin/**");
            }
    
            @Override
            public void configure(WebSecurity web) {
                web.ignoring().antMatchers(
    
                        // статика backend
                        "/backend/css/**",
                        "/backend/js/**",
                        "/backend/fonts/**",
                        "/backend/images/**",
                        "/backend/init/**"
    
                );
            }
    
    
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.userDetailsService(userDetailsService);
            }
    
        }
    
    
        // Конфигурация для frontend (Обычная авторизация и авторизация oauth2)
        @Configuration
        @Order(2)
        public static class FrontendConfigurationAdapter extends WebSecurityConfigurerAdapter {
    
            @Autowired
            private CustomWebAuthenticationDetailsSource authenticationDetailsSource;
    
            public FrontendConfigurationAdapter() {
                super();
            }
    
            protected void configure(HttpSecurity http) throws Exception {
    
                http
                        .authorizeRequests().mvcMatchers("/robots.txt").permitAll()
                        .antMatchers(
                                "/", "/auth", "/signup", "/restore", "/activation/**",
                                "/admin/login", "/admin_restore",
                                "/attachments/get/**",
                                "/sendMessage",
                                "/error",
                                "/page/**",
                                "/categories", "/categories/**",
                                "/terms/**", "/posts", "/posts/**"
                        ).permitAll()
                        .anyRequest().authenticated()
    
                        .and()
                            .formLogin()
                            .authenticationDetailsSource(authenticationDetailsSource)
                            .loginPage("/auth")
                            .loginProcessingUrl("/auth")
                            .usernameParameter("email")
                            .passwordParameter("password")
                            .defaultSuccessUrl("/")
                            .failureUrl("/auth?authError")
                            .permitAll()
    
                        .and()
                            .rememberMe()
                            .rememberMeParameter("remember-me")
                            .tokenValiditySeconds(86400)
    
                        .and()
                            .oauth2Login().defaultSuccessUrl("/")
    
                        .and()
                            .logout()
                            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                            .logoutSuccessUrl("/")
                            .deleteCookies("JSESSIONID")
    
                        .and()
                            .exceptionHandling()
                            .accessDeniedPage("/403")
    
                        .and()
                            .csrf()
    
                        .and()
                            .sessionManagement()
                            .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
    
                        .and()
                            .headers().frameOptions().sameOrigin();
                        
    
            }
    
            @Override
            public void configure(WebSecurity web) {
                web.ignoring().antMatchers(
                        "/frontend/css/**",
                        "/frontend/js/**",
                        "/frontend/fonts/**",
                        "/frontend/images/**",
                        "/frontend/lib/**",
                        "/frontend/vendor/**"
                );
            }
    
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.userDetailsService(userDetailsService);
            }
    
    
        }
    
    }


    Админа, как по мне, лучше создать при инициализации приложения. Вот, код, как это делаю я.

    @Component
    public class InitData implements CommandLineRunner {
    
    @Override
    public void run(String... args) throws Exception {
    if (userServiceImpl.usersCount() == 0)
                initUsers();
    }
    
    private void initUsers() {
    // создаем пользователей
    }
    }
    Ответ написан
    3 комментария
  • Как исправить ошибку в консоли браузера "Access to XMLHttpRequest?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Ответ написан
    Комментировать
  • Почему не отображаются изображения из static resources?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Стоп!

    Итак, не отображается картинка. В консоли ошибка 404.

    Решение - Build Project (ctrl + F9) или Rebuild Project и Спринг увидит вашу новую статику и все будет работать.

    + если используете Spring Security, то:

    @Override
            public void configure(WebSecurity web) {
                web.ignoring().antMatchers(
    
                        // статика
                        "/css/**",
                        "/js/**",
                        "/fonts/**",
                        "/images/**"
                );
            }
    Ответ написан
    2 комментария
  • Как подставить класс в Thymeleaf?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    Вы можете сделать это на уровне thymeleaf

    Что-то типа такого должно сработать:

    <li><a href="#" class="link-item" th:classapend="${#strings.contains(#httpServletRequest.requestURI, '/tutorials')} ? colorClass : baseClass">Tutorials</a></li>


    Тут мы из запроса получаем URL, проверяем содержится ли в URL /tutorials и если да, то добавляем класс colorClass, а если нет, то baseClass.

    Изучите th:class & th:classappend
    Ответ написан
    3 комментария
  • Как загрузить template по другому url без перезагрузки страницы в Thymeleaf?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    при клике на кнопку менятся url на другой и вызывается другой темплейт

    Изучите технологию ajax. А также SPA (single page application)

    всплывающее модальное окно с другим темплейтом

    можно просто отправить ajax запрос на сервер. Вернуть шаблон у уже подставленными значениями и показать модальное окно
    Ответ написан
    Комментировать
  • Как создать скроллБар на thymeleaf возвращающий строку?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Скажу честно, не совсем понял, что вы имеете ввиду под "скроллбар"ом, в данном контексте...
    На основании этого текста:
    Но мне надо, допустим ,поднимать данные с базы поддерживаемых растений (только имена допустим), после эти имена надо каким то образом передать на страницу в скролл бар, чтобы пользователь уже из них выбрал растение и при сабмите

    предположу, что вы хотите создать dropdown список со скроллбаром, откуда пользователь может выбрать данные.

    Для реализации нужно сделать следующее:
    1) реализовать метод, который возвращает список (List) или массив строк с названиями растений. (в Repository).
    2) В GET запросе при открытии страницы через Model ( model.addAttribute("plants", repo.getPlants()) ) передать эту информацию.
    3) далее остается все это дело красиво завернуть в dropdown список.
    https://getbootstrap.com/docs/4.3/components/dropdowns/

    Возможные растение вы можете хранить, как в БД (если предполагается частое добавления и удаление разновидностей) или хранить в виде Enum.

    .... как обработать выбранное значение в контроллере ...

    зависит от того, что вы выберете. В методе контроллера можно принять объект и сохранить его, можно принять enum и сохранить его, можно принять String и используя String.valueOf(myString) сохранить enum и т.д.
    Ответ написан
    2 комментария
  • Как организовать клиентскую часть на thymeleaf?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Вот, пример метода в контроллере и соответственно шаблонизатор.

    Предположим, что у вас есть страница со списком "коробок".
    Например,

    @GetMapping("/boxes")
    public String getBoxesList(
    	Model model
    ) {
    
    	model.addAttribute("boxes", boxService.getBoxesList());
    	return "/boxes-template";
    }

    <!DOCTYPE html>
    <html>
    <head>
    	<title></title>
    </head>
    <body>
    
    	<!-- тут итерируем по списку -->
    	<div th:each="box : ${boxes}">
    		<span th:inline="text">[[${box.boxTitle}]]</span> <!-- или можно так -->
    		<span th:text="${box.boxTitle}">Название коробки</span>
    		<a th:href="'/boxes/edit/'+${box.boxId}">Изменить</a> <!--обратите внимание на эту ссылку -->
    	</div>
    
    </body>
    </html>


    По клику на кнопку изменить открываем форму с информацией о боксе
    @GetMapping("/boxes/edit/{id}") 
    public String boxEditForm(
    	@Pathvariable("id") Long id,
    	Model model
    ) {
    	Box box = boxRepository.findById(id);
    	model.addAttribute("box", box);
    	return "box-edit-template";
    }

    А все остальное уже делается также....

    Вы можете по-разному реализовать. Например, передавать id бокса не в виде pathVariable, а RequestParam. Можно использовать POST запрос, если вы не хотите передавать id в URL и т.д. Я описал простой вариант.

    Если я правильно понял ваш вопрос, то это то, что вам нужно...
    Ответ написан
    2 комментария
  • Как в thymeleaf дублировать поле для сравнения?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Здравствуйте!
    Вы сильно заморочились, если честно... тут даже Spring ни причем, а больше базовые знания html + js
    Вот, ваша форма:
    <form th:action="@{/register}"
          th:object="${personForm}" method="POST">
        Login:
        <input type="text" th:field="*{name}" />
        <br/>
        Email:
        <input type="text" th:field="*{email}" />
        <br/>
        Password:
        <input type="text" th:field="*{password}">
        <br/>
        Confirm password:
        <input type="text" th:field="*{doublePassword}">
        <input type="submit" value="Create" />
    </form>


    1) небольшой совет используйте специфические инпуты. Например, если вам нужно поле майл, то используйте майл и т.д.
    Соответственно:
    <form th:action="@{/register}"
          th:object="${personForm}" method="POST">
        Login:
        <input type="text" th:field="*{name}" />
        <br/>
        Email:
        <input type="email" th:field="*{email}" />
        <br/>
        Password:
        <input type="password" th:field="*{password}">
        <br/>
        Confirm password:
        <input type="password">
        <input type="submit" value="Create" />
    </form>

    2) Определитесь с тем, где именно вы хотите валидировать совпадение пароля на клиенте или на сервере. Как по мне, на клиенте лучше, чтобы лищний раз не нагружать сервер.
    Если на клиенте, то средствами js, если на сервере, то средствами java. Рассмотрим оба варианта:
    - На сервере. Получаете оба пароля из формы, сравниваете через equals() и возвращаете нужный результат. Если пароль неверен, то можете добавить сообщение через model.addAttribute() и вывести в шаблоне
    @RequestMapping(value = {"/register"} , method = RequestMethod.POST)
    public String savePerson(Model model, @ModelAttribute("personForm") UserForm personForm) {
    
    
    	if(!personForm.getPassword.equals(personForm.getPasswordConfirmation)) {
    		model.addAttribute("passwordIncorrect", "Вы ввели некорректный пароль");
    		return "register";
    	}
    
        if(personForm.checkPassword() &&
                userRepository.findByEmail(personForm.getEmail()) == null &&
                userRepository.findByName(personForm.getName()) == null) {
    
            AppUser user = new AppUser(personForm.getName(),
                    personForm.getEmail(),
                    personForm.getPassword());
    
            user.setEnabled(true);
            user.setRoles(Collections.singleton(Role.USER));
            userRepository.save(user);
            return "home";
         }
    
        return "register";
    }


    - Обработка на клиенте средствами js. Идея заключается в том, что вы деактивируете кнопку "Регистрация" и только если пароли совпадают, то активируете кнопку.

    Тут материалов хватает. Наберите в гугл jquery password validation.
    Вот, пример:
    Скрипт - https://www.jqueryscript.net/form/Password-Strengt...
    Демо - https://www.jqueryscript.net/demo/Password-Strengt...
    Ответ написан
    1 комментарий
  • Как отобразить изображение на странице в spring, thymeleaf?

    azerphoenix
    @azerphoenix Куратор тега Spring
    Java Software Engineer
    Здравствуйте!
    Начнем с самого простого...
    Предположим, что вы загрузили изображения в папку img, которая лежит в static ( resources/static/img/myImage.jpg )
    Если вы используете шаблонизатор thymeleaf, то в шаблоне нужно задать:
    <img th:src="@{img/myImage.jpg}" alt=""/>
    У вас на скрине ошибка, так как вы просто вставили картинку в атрибут src, а пути некооректные (404 ошибка), так как не учтен context path. У вас наверняка приложение открывается по урлу localhost:8080/contextPath
    Кстати, урлы к внутренним ссылкам, скриптам и стилям задаются точно также ( th:href="@{/url/url}" )

    Также заметил небольшой баг, с чем это связано не знаю, возможно intellij idea, возможно spring boot...
    В общем, если даже перезапустить приложение, даже при корректных путях к файлам может быть 404 или 500 (насколько я понял, это происходит при настроенном spring security). Чтобы избежать этих ошибок, нужно сделать Rebuild project, а потом запустить. Столкнулся с этим только в одном проекте.
    Ответ написан
    6 комментариев
  • Как сохранить текст из полей в базу данных с Thymeleaf и Spring Boot?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Здравствуйте!
    У вас выбрасывает исключение связанное с шаблонизатором thymeleaf
    + вы не указали атрибут name для инпутов
    А теперь, простой пример
    @Entity
    Car {
    	
    	@Id
    	@GeneratedValue(strategy = GenerationType.AUTO)
      	private Long id;
    
      	private String carModel;
    
      	private String carName;
    
    	public Car() {}
    	//getters & setters & no args constructor
    }


    <form method="post" th:action="@{/cars/add}">
    	
    	<input type="text" name="carModel">
    	<input type="text" name="carName">
    	<input type="submit" value="Добавить машину">
    
    </form>

    @Autowired
    private CarRepository carRepository
    
    @PostMapping("/cars/add")
    public String addNewCar(
     @ModelAttribute("carModel") String carModel,
     @ModelAttribute("carName") String carName,
     Car car
    ) {
     // тут конечно можно в carService создать метод и передать ему эти аргументы
    	car.setCarModel(carModel);
    	car.setCarName(carName);
    	carRepository.save(car);
    
    	return "main"; 
    }


    Видео - https://www.youtube.com/watch?v=jH17YkBTpI4&list=P...
    Ответ написан
    2 комментария