I am a rookie Grails user and I am completely new to AJAX. I am not exactly grasping the concept of AJAX, and the material online is fragmented.
From my understanding, in grails if I wanted to execute a method in one of my controllers when a part of my HTML doc loads I could simply use something along the lines of
<div onload="${remoteFunction(action:"foo", update:"foo"...)}" ...>
How is the response from the call to the hypothetical foo returned and how can I access it in a js function?
Can I return an object from a class I created from my hypothetical foo action?
On the return of the foo action you can put simple html as text or render some objects that can be used in the view.
Here you have all the info about the Controller "render"
http://grails.org/doc/latest/ref/Controllers/render.html
You can have a that will be update with that data and work there with it. Then you can access to the Html and data inside that "foo" div with javascript like you usually do.
For example:
Controller.groovy
// renders text to response
render '<div id="bar" onclick="alert($('bar').val())>some text</div>'
View.gsp
//Makes the call and updates foo
<div onload="${remoteFunction(action:"foo", update:"foo"...)}" ...>
<div id="foo" name="foo"></div>
Output
<div onload="theAjaxJavascriptFunctionThatGrailsWillInject" ...>
<div id="foo" name="foo">
<div id="bar" onclick="alert($('bar').val())">some text</div>
</div>
I you return some object from the Controller.grooy then you have to treat it like this in you View.gsp
//Makes the call and updates foo
<div onload="${remoteFunction(action:"foo", update:"foo"...)}" ...>
<div id="foo" name="foo">
${myObject.theValueIWant}
</div>
I added a javascript alert but you can do it the way you like, there are lots of ways to do it.
Hope it helps :)
Related
I am trying to do same small service and sometimes I am sending requests to my Rest Api.
I am getting two problems:
How to fill field in form in html using thymeleaf? I have form like this and would like to fill fields with current values (th:value="${}" - not working for me):
<form th:action="#{/offences/edit}" method="post" th:object="${createOffenceForm}"> <input th:field="*{name}" th:value="${currentOffence.name}"/> <input th:field="*{penaltyPoints}" th:value="${currentOffence.penaltyPoints}"/> <input th:field="*{amountOfFine}" th:value="${currentOffence.amountOfFine}"/> <button type="submit">UPDATE</button> </form>
The problem is with loading css styles to html when I redirect to the site with path variable. For example i created html with two buttons. First one is hardcoded:
<a th:href="#{/offences/test}" class="link" style="text-decoration: none"><button class="buttonOK" type="submit">EDIT</button></a>
after redirect my site looks like this (everything works, it should be like that):
`
and here is after second button - redirect with path variable:
<a th:href="#{'/offences/edit/' + ${offence.id}}" class="link" style="text-decoration: none"><button class="buttonOK" type="submit">EDIT</button></a>
and view after load:
For the issue with your form data not being filled, this is because th:field and th:value are not meant to be used at the same time. So th:field ends up overwriting you value.
In your case I would suggest either manualy setting name (and id) or replacing th:object with the a filled version of the bean you want to return and only using th:field
As for the second issue it looks like a faulty fragment return on a post call to me but need to atleast have the controller functions and more complete html to say anything about that. And in general it's advisable to have 1 question per problem and not bundle things.
Ok so i found soultion.
To fill fields in form I only had to add to ModelMap form with fields.
Something like this and thymeleaf autofilled fields in html.
modelMap.addAttribute("createOffenceForm", new CreateOffenceForm(offenceDTO.getName(), offenceDTO.getPenaltyPoints(), offenceDTO.getAmountOfFine()));
Second solution is simple too. When changed mapping in Controller from #GetMapping("/path/{id}") to #GetMapping("/path-{id}") - everything works. There is no other problems with styles...
So I tried to make a URL Shrinker for fun and used the Traversy Media tutorial for it.
Here's the GitHub of his project: https://github.com/bradtraversy/url_shortener_service
In order to make it into a full Website, I have to make a UI...
But I am stuck with the POST request I am generating via ejs.
I currently have written this code in my index.ejs:
<body>
<header class="header">
<h1>Shrinker</h1>
</header>
<div class='main'>
<form action="http://localhost:5000/api/url/shorten" method="POST" class="content">
<h2 class="shortenheader">Url Shortener</h2>
<div class="input-group">
<input type="shortUrl" class='neumorph-input' placeholder='Your Link' />
<input type="submit" class='neumorph-btn'>
</div>
</form>
</div>
</body>
But it always gives me the error "Invalid long url" which is coming from the code structure of the file in routes/url.js.
In the tutorial he used a simple POST request with a content-type of application/json. Which worked just fine. I am using mongodb atlas, so the cloud version of the database.
POST http://localhost:5000/api/url/shorten
Content-Type: application/json
{
"longUrl": "https://www.stackoverflow.com"
}
I guess I have to use that type in my index.ejs but I don't know how to do that. Can somebody please help?
Well, a HTML form is sent as an urlencoded string (just like the GET query string) or form-data (which can contain attachments), never JSON. See https://www.w3.org/TR/html401/interact/forms.html#adef-enctype
To achieve that, you will have to make an AJAX request, in which you'll be able to send whatever data you want.
To form the request body you could serialize the form and then convert it to JSON. Or, the easiest way since there's only one property, you could build the body manually: var data=JSON.stringify({longUrl:$("#shortUrl").val()});.
Also, you have an error. type="shortUrl" is not valid. It should be type="text" and name="shortUrl", or id="shortUrl" to be able to select it with $("#shortUrl").
If you've never done it, the easiest way would be with jQuery. See https://api.jquery.com/jquery.ajax/
I create html components with Thymeleaf. Components are declared in separate file:
Declaration of basic button in buttons.html
<div th:fragment="btn-basic" class="btn btn-basic" th:text="${text}" th:classappend="${class}">
Button
</div>
The idea is to provide some type of tool-set for components. Code for using this component will be:
<div th:replace="~{buttons :: btn-basic (text='Button Text', class='button-class')}"></div>
It's working well, but I think about case when button need to have attributes like: onclick="..." or data-customAttr="..." or any other attribute. And here goes the problem:
How to pass attributes to button?
One way is to pass it as parameter of fragment, but it's too ugly.
Is there any way to get attributes of placeholder in fragment? (see example below)
This how I want to call fragment:
<div th:replace="~{buttons :: btn-basic (text='Button Text', class='button-class')}" onclick="..." data-customAttr="..."></div>
and in btn-basic fragment want to get these attributes and attach to it. Something like this:
<div th:fragment="btn-basic" class="btn btn-basic" th:text="${text}" th:classappend="${class}" onclick="..." data-customAttr="...">
Button
</div>
Any ideas?
I had a similar idea, but the question is, if the customizing of a component is as complex as the result, what is the benefit?
Btw. with the Thymeleaf Layout Dialect you can do something like this: https://ultraq.github.io/thymeleaf-layout-dialect/Examples.html#reusable-templates, I favor that, instead of the everything-as-parameter approach.
Is a bad practice to be creating divs in your HTML just to be able to have multiple controllers?
Since you cannot have them in one also the approach of having multiple directives with separate controllers seems like a hack, but correct me if I am wrong.
So I have 2 controllers - one is called ConvertController and the other one is called YouTubeLinkUIController.
The responsibility of the first one is to hit my Web API controller and convert the given YouTube link into an audio.
The responsibility of the second one is to control some of the UI functionality.
In order to follow the single responsibility principle, I have separated them into 2 different controllers and therein lies the problem.
My last commit, specifically Index.cshtml shows the full code, but for the sake of not making this messy I will abreviate it for this post.
https://github.com/AvetisG/TubeToTune/commit/e68ba42f20498c9a4e032328aed6d68c27b9b2cb
<div class="container" ng-app="TubeToTuneApp" ng-controller="YouTubeLinkUIController">
... more code
#*Scoping the Convert Controller*#
<div ng-controller="ConvertController">
... more code
</div>
</div>
Looking at your github commit message it looks like you split the controllers because you didn't want to have too much code in your ui controller.
This is a perfect example of when an angular service is in order. You can think of a service like a controller without all the overhead and that can be easily called from another controller
Angular Service Documentation
What you should do is define a service that calls your api and then
angular.module('youTube', [])
.controller('YouTubeLinkController', ['$scope', 'convert',
function($scope, convert) {
$scope.convertLink = function() {
$scope.convertMessage = convert.convertYoutube()
};
}
])
.factory('convert', [
function() {
var convertService = {};
convertService.convertYoutube = function(data) {
//Make api call
return "Conversion Done";
}
return convertService;
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="youTube">
<div ng-controller="YouTubeLinkController">
{{convertMessage}}
<button ng-click="convertLink()">Convert</button>
</div>
</body>
Naturally you should define the service in it's own page. That way controllers are only used for interacting with UI. It also solves your problem
It's absolutely fine to use divs\spans and nested structures and use ng-controller with it.
When you add ng-controller to a html element in a way you are creating a component with a model-view and controller.
A complex component can have nested sub components that perform very specific functionality and in a way you are demarcating such parts with ng-controller . To take it a step further if you convert these component into directives, with their own controller + template(view) and that accept model from a data source, then you have a reusable component that you can use throughout the app.
To me, it looks more like your ConvertController should be written as a service, rather than as a controller. This would still follow single responsibility, but would probably align closer to your intended functionality: UI functionality and view data in the controller, business logic in the service.
That said, having two controllers on a single view might not be bad practice, this leads to confusing mark-up in cases like the following:
<button type="submit" class="btn btn-xlarge btn-warning btn-block" ng-if="AreThereYouTubeLinks() == true" ng-click="ConvertToTunes(YouTubeLinks)">
You may know personally which controllers AreThereYouTubeLinks(), ConvertToTunes(), and YouTubeLinks belong to, but this will be confusing in the long-term (and can lead to issues with scope-bound variables like YouTubeLinks.
Luckily, there's a syntax for helping clear this up- Controller As - and Todd Motto wrote an excellent article explaining how to use it and what problems it helps solve. In brief, it would turn something like this:
<div ng-controller="MainCtrl">
{{ title }}
<div ng-controller="AnotherCtrl">
{{ title }}
<div ng-controller="YetAnotherCtrl">
{{ title }}
</div>
</div>
</div>
in to this:
<div ng-controller="MainCtrl as main">
{{ main.title }}
<div ng-controller="AnotherCtrl as another">
{{ another.title }}
<div ng-controller="YetAnotherCtrl as yet">
{{ yet.title }}
</div>
</div>
</div>
In your case, you would end up with this safer, more understandable mark-up:
<div class="container" ng-app="TubeToTuneApp" ng-controller="YouTubeLinkUIController as linkCtrl">
... more code
#*Scoping the Convert Controller*#
<div ng-controller="ConvertController as converter">
<button type="submit" class="btn btn-xlarge btn-warning btn-block" ng-if="linkCtrl.AreThereYouTubeLinks() == true" ng-click="converter.ConvertToTunes(linkCtrl.YouTubeLinks)">
</div>
</div>
If you're going to stick with two controllers, you probably want to consider investing the time to getting comfortable with Controller As.
I'm fairly new at MVC 3 but I came across a rather curious problem.
I'm using the Razor syntax, and according to VS, I don't need to prefix # to statements if they are immediately preceded with another # statement, or once inside the code, the prefix # is no longer required.
So here is my code in my View:
#using (Html.BeginForm("StudentSelect", "Home", FormMethod.Post, new { id = "sSelect" })) {
Html.HiddenFor(m => m.SelectedStudent);
foreach (Classes.CStudent item in Model.Students)
{
<div class="studentSelect">
<div class="studentname">#item.StudentName</div>
<div>#item.Address</div>
</div>
}
}
Take not that the Html.HiddenFor and foreach lines do not have # prefix.
The generated HTML should produce a <form> followed by a <input type="hidden"> field.
However, upon checking the HTML on the generated page, the hidden input field is missing.
<form action="/Home/StudentSelect" id="sSelect" method="post">
<div class="studentSelect">
<div class="studentname">Name1</div>
<div>AAA</div>
</div>
<div class="studentSelect">
<div class="studentname">Name2</div>
<div>Address1</div>
</div>
</form>
Am I doing something wrong? Why isn't the hidden input not rendered?
Any clues would help. Thanks
By the way, this code compiles correct. However, if I prefix # in front of Html.HiddenFor my code does not compile and Visual Studio produces an error.
The # serves two purposes. One to trigger "code mode" and one to shorthand Response.Write.
The instance of # preceding the using triggers "code mode".
The instance of # preceding item.Address is equivalent to Response.Write(item.Address);.
You still require it for Html.HiddenFor as this returns an MvcHtmlString output that needs to be written to the response stream.
This line:
Html.HiddenFor(m => m.SelectedStudent);
should be:
#Html.HiddenFor(m => m.SelectedStudent)
Notice the ; is no longer required.