I'm trying to write a test for file uploading in grails. I'm using spock as my testing framework.
I have a javascript function that link to a button on my view that adds an <input type="file"> to my form whenever it is clicked.
It looks something like this:
<script>
var fileNum = 1;
function addUploader() {
fileNum++;
var fileInputTag = "<input type=\"file\" name=\"myfile." +fileNum+ "\"/>";
var uploadField = document.getElementById("uploadFields");
var row = uploadField.insertRow(fileNum + 1);
var cell = row.insertCell(0);
cell.innerHTML = fileInputTag + "<br/>";
}
</script>
<tr>
<td>
<input type="file" class="required" name="myfile.1" />
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>
More Files <br />
<br/>
<input type="submit" value="Download"/>
</td>
</tr>
My controller looks something like this:
def uploadFile(){
List<MultipartFile> files = []
params.myfile.each {
if (it.value.isEmpty()) {
flash.message = message(code: 'upload.empty.message')
redirect(uri: "/")
return
}
files.add((MultipartFile) it.value)
}
def faxPreviews = []
faxPreviews = uploadFileService.generateFaxPreviews(files)
render(view:'/uploadfile/index', model:[p:uploadFileService.decodeFaxPreviews(faxPreviews)])
}
I want to test that a flash message is shown if the list is empty but I don't know how to put that in the test. I've been trying to search how to mock the view params in the test but so far no go.
This was actually very easy. D'oh! I ended up with this test.
void "updloadFile should warn when no files are attached."() {
given:
def file = new GrailsMockMultipartFile('mockFile', ''.bytes)
Map<MultipartFile> files = [:]
files[1] = file
controller.params.myfile = files
when:
controller.uploadFile()
then:
response.redirectedUrl == '/'
flash.message != null
}
My problem was that I thought the myfile in the view was a key when it is actually the name of a map. So myfile.1 would turn into a myfile[1] = "whatever value is passed to input"
Related
I have this UI
And my goal is to update table after clicking Update button based on Id and Description parameters
I have following code
Html
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Products)
{
<tr>
<td>#item.Id</td>
<td>#item.Description</td>
</tr>
}
</tbody>
</table>
<label>Id</label>
<input data-bind="value: productId" type="text" class="form-control" />
<br />
<label>Description</label>
<input data-bind="value: productDescription" type="text" class="form-control" />
<br />
<button class="btn btn-primary" data-bind="click: update">Update</button>
JavaScript
<script type='text/javascript'>
// Plain javascript alternative to jQuery.ready.
(function () {
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = json.products;
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = null;
self.productDescription = null;
// Update function.
self.update = function () {
// Get products collection index.
index = self.products.findIndex(function (product) {
return product.Id == self.productId
});
// Throws -1, index was not found !
alert(index);
// Assign new value.
// self.products[index].description(self.productDescription);
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
})();
</script>
I need help with two things
Update knockout viewmodel (self.products collection based on Id and Description inputs in viewModel.Update function)
Update table (I don't know how to bind the rows, when using razor foreach)
EDIT:
When I change Razor loop with Knockout foreach
<tbody data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
it does not work either :/ I tried some changes with observables
<script type='text/javascript'>
// Plain javascript alternative to jQuery.ready.
(function () {
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = ko.observable(json.products);
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = ko.observable();
self.productDescription = ko.observable();
// Update function.
self.update = function () {
self.products()[self.productId()-1].description = self.productDescription()
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
})();
</script>
I do not get an error, but the value is not changed in the html table.
Your knockout code looks mostly correct but your UI isn't updating because the values that you're changing aren't observable. If you want the changes to the items to reflect in the table you need to make each field that will update an observable instead of having a single observable property for the entire json object.
The following code will only update if the entire object reference changes. IE if you fill self.products with a new object. It will not update if you change a property on the existing object.
// Product array from server.
self.products = ko.observable(json.products);
You'll need to change your code to individually apply observable properties to your json object when it comes from the server.
// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
self.products.push({
id: json.products[i].id,
description: ko.observable(json.products[i].description)
});
}
And then you'll have to update the value using the function style getter/setter.
// Update function.
self.update = function () {
self.products()[Number(self.productId())-1].description(self.productDescription());
};
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
self.products.push({
id: json.products[i].id,
description: ko.observable(json.products[i].description)
});
}
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = ko.observable();
self.productDescription = ko.observable();
// Update function.
// Update function.
self.update = function () {
self.products()[Number(self.productId())-1].description(self.productDescription());
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
</table>
<br/>
<label>Id: </label>
<input data-bind="value: productId" type="text" class="form-control" />
<br />
<label>Description: </label>
<input data-bind="value: productDescription" type="text" class="form-control" />
<br />
<button class="btn btn-primary" data-bind="click: update">Update</button>
I have this html:
Picture with html code
I need to show in my View : Бавария Майнц -2.25
I try write this:
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(
"https://www.parimatch.com/sport/futbol/germanija-bundesliga");
foreach (HtmlNode table in htmlDoc.DocumentNode.SelectNodes("//div[#id='oddsNote']/table/tbody"))
{
foreach (HtmlNode row in table.SelectNodes("tr"))
{
HtmlNodeCollection cells = row.SelectNodes("td");
if (cells == null)
{
continue;
}
foreach (HtmlNode cell in cells)
{
ViewBag.Results += cell.InnerText;
}
}
}
but my table always null. Where I have a mistake?
and what are the other options to output in View except Viewbag?
My HTML:
<div id ="z_container">
<div id = "Z_contentw">
<div id = "OddList">
<form name ="f1" id = "f1">
<div class = "container_grey">
<div class = "wrapper">
<table id = "4529899" class ="dt_twp">
<tbody class ="row1 processed">
<tr class ="bk">
<td> "02/03" <br> "21:00"</td>
<td class ="l"> <a class ="om" id ="a738">Bavaria Mainc</a></td>
<td> <b 3.5></td>
</tr>
</tbody>
<tbody class ="row2 processed">
<tr class ="bk">
<td> "03/03" <br> "19:00"</td>
<td class ="l"> <a class ="om" id ="a739">Roma Milan</a></td>
<td> <b 2.5></td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
</div>
</div>
I need to show: 02/03 21:00 Bavaria Mainc 03/03 19:00 Roma Milan
Your xpath has an id that is not in the html you provided other than that
If you want the text in one line as you showed then it can be
var text = string.Join(" ", doc.DocumentNode.SelectNodes("//tr[#class='bk']//text()[normalize-space()]").Select(t=>t.InnerText));
If you want to model the data then use the indices of the td, the first one has the time the second has the teams so create a simple model
class FootballMatch
{
public string Time;
public string Teams;
}
and get the data using the following
var matches = doc.DocumentNode.SelectNodes("//tr[#class='bk']").
Select(tr => new FootballMatch() {
Time = string.Join(" ", tr.SelectNodes("./td[1]//text()").Select(t => t.InnerText)),
Teams = tr.SelectSingleNode("./td[2]//text()[normalize-space()]").InnerText,
});
In my Grails application I have multiple pages that list a group of data objects inside of a table. In these pages I provide a search function which when performed will adjust the table to only display the data objects that match the query. However If a user decides to delete one of these data objects the application will take them back to the default table which displays everything. I would like for query results to remain intact after performing a delete.
I will use my "Skill Evaluations" page as my example in this post.
Here is the relevant code in the domain class
SkillEval.groovy
class SkillEval {
static hasMany = [lines: SkillEvalL, courses: CourseOffering, choiceLabels: ChoiceLabel]
String name
String formVersion
static def search(params) {
def criteria = SkillEval.createCriteria()
def results = criteria.list(params) {
or {
ilike("name", params.search+'%')
}
}
return results
}
}
Relevant section of the gsp view file
list.gsp
<g:form>
<div class="search">
<label for="searchField">Search:</label> <input type="text"
id="searchField" name="search" value="${params.search}" /> <input
type="submit" value="Search" />
</div>
<br>
<table id="mainTable">
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<g:each var="eval" in="${skillEvalList}">
<tr>
<td>
<strong>
${eval.name}
</strong>
</td>
<td>
${eval.formVersion}
</td>
<td>
<g:actionSubmit value="Delete" controller="skillEval" action="delete" onclick="setId(${eval.id});return confirm('Are you sure?');" class="delete" />
</td>
</tr>
</g:each>
</tbody>
</table>
<g:if test="${(skillEvalCount/maxCount) > 1}">
<div class="pagination">
<g:paginate action="list" total="${skillEvalCount}" />
</div>
</g:if>
<input id="evalId" type="hidden" name="id" value="" />
</g:form>
</div>
<r:script>
function setId(id)
{
$('#evalId').val(id);
}
</r:script>
Relevant code in the Controller class
SkillEvalController.groovy
def delete(Long id) {
def skillEval = SkillEval.get(id)
if (skillEval) {
def allInstances = SkillEvalI.findAllByForm(skillEval)
allInstances.each { evalInstance ->
evalInstance.lines.clear()
if(!evalInstance.delete()) {
println "Failed to delete skill eval instance"
}
else {
println "Instance successfully deleted."
}
}
try {
skillEval.delete(flush: true)
}
catch (DataIntegrityViolationException e) {
}
}
redirect(action: "list")
}
How can I make the view retain the queried results after deleting one of the queried items?
When you redirect to the list action you can provide parameters -- just capture the parameters (if any) in the delete action and pass them in the redirect call to list at the end of the controller action.
(Update: Forgot that actionSubmit does not accept params as an attribute, so cobbled together this solution from this SO answer and the Grails docs)
Example:
// View (list.gsp)
<g:actionSubmit action="deleteWithParams" value="Delete" ... />
// Controller
def list() {
def deleteWithParams = { forward(action:'delete', params:[search: params?.search]) }
render...
}
def delete(Long id) {
// Deleting the skillEval ...
redirect(action: "list", params: [search: params?.search])
}
I want to make a select box in grails. I am using 2.1.0. I have a view page which shows a select box named class. But it does not shows any value. The list I have used in the from attribute of select works fine is browser when I render it as json. Can anyone make my combo box work for me please ? Here is my code below :
my view page >>>
<g:form controller="admistratorAction" action="addStudent">
<table class="centerTable">
<div class="height"></div>
<tr>
<td><label>Full Name :</label></td>
<td><g:textField name="fullname" id="fullname" class="field"/></td>
</tr>
<tr>
<td> <label>Admission Class :</label></td>
<td><g:select name="class" id="class" class="field" from="${classList}" noSelection="['':'-Choose a class-']"/></td>
</tr>
<td colspan="2" align="right"><g:submitButton name="createSubmit" value="Create" class="button" onclick="return confirm('Are you sure???')"/></td>
</tr>
</table>
</g:form>
here is my controller >>
package administrator
import common.classes.Classes
import grails.converters.JSON
class AdmistratorActionController {
def addStudent = {
render "add student"
}
def classList = {
def classes = Classes.executeQuery("SELECT c.classes FROM Classes c")
def all_class = [classes : classes]
render all_class as JSON
}
}
You do not need to convert it to JSON in order to have it in gsp page
class AdmistratorActionController {
def addStudent = {
def n = params.fullName
def c = params.class
// do something with them
}
def classList = {
def classes = Classes.list()
// pass details to view 'classList'
[classList : classes]
}
}
Create -> views/administratorAction/classList.gsp
have you form ready and you will be able to get ${classList} in it
Hi I have looked around online for hours trying to solve this but I couldn't find a solution.
I am building the solution using ASP.NET MVC and Knockout. The controller returns the options for the user to select from to the view which converts it into JSON and uses the mapping plugin to map it to a knockout View Model. It populates correctly because when I do this: ko.toJSON($data) to test it, the correct data is returned but the view won't update the list when an item is added to it.
My guess is that the properties aren't observables but I'm not sure how to fix that.
This is an example of the JSON Returned:
[{"Key":1,"Value":"company"}]
This is my javascript:
function ProjectWorkedOn() {
var self = this;
self.client = ko.observable();
self.job = ko.observable();
self.project = ko.observable();
self.workType = ko.observable();
}
function createTimesheetViewModel() {
var self = this;
//list of options
self.UserClients = ko.observableArray();
self.UserProjects = ko.observableArray();
self.UserJobs = ko.observableArray();
self.UserWorkTypes = ko.observableArray();
//keep track of selected options
self.selectedClient = ko.observable();
self.selectedProject = ko.observable();
self.selectedJob = ko.observable();
self.selectedWorkType = ko.observable();
//list to add choices in
self.ListProjectsWorkedOn = ko.observableArray();
self.addProjectWorkedOn = function () {
var project = new ProjectWorkedOn();
project.client = self.selectedClient;
project.job = self.selectedJob;
project.project = self.selectedProject;
project.workType = self.selectedWorkType;
self.ListProjectsWorkedOn.push(project)
}
self.removeProjectWorkedOn = function (projectWorkedOn) {
self.ListProjectsWorkedOn.remove(projectWorkedOn)
}
}
$(function () {
var CreateTimesheetViewModel = new createTimesheetViewModel();
CreateTimesheetViewModel.UserClients = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserClients))');
CreateTimesheetViewModel.UserProjects = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserProjects))');
CreateTimesheetViewModel.UserJobs = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserJobs))');
CreateTimesheetViewModel.UserWorkTypes = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserWorkTypes))');
ko.applyBindings(CreateTimesheetViewModel, document.getElementById("CreateTimesheet"));
});
This is the part of the view that adds items to the list:
<table>
<tr>
<td>
Projects Worked On:
</td>
<td>
Client: <select data-bind="options: UserClients, optionsText: 'Value', value: selectedClient"></select>
</td>
<td>
Project: <select data-bind="options: UserProjects, optionsText: 'Value', value: selectedProject"></select>
</td>
<td>
Job: <select data-bind="options: UserJobs, optionsText: 'Value', value: selectedJob"></select>
</td>
<td>
Service: <select data-bind="options: UserWorkTypes, optionsText: 'Value', value: selectedWorkType"></select>
</td>
<td>
<button data-bind="click: addProjectWorkedOn">Add Project</button>
</td>
</tr>
</table>
and this is what displays them:
<table>
<tr>
<td>Client</td>
<td>Project</td>
<td>Job</td>
<td>Service</td>
<td></td>
</tr>
<tbody data-bind="foreach: ListProjectsWorkedOn()">
<tr>
<td data-bind="text: client.Value"></td>
<td data-bind="text: project.Value"></td>
<td data-bind="text: job.Value"></td>
<td data-bind="text: workType.Value"></td>
<td><button data-bind="click: $parent.removeProjectWorkedOn">Remove</button></td>
</tr>
</tbody>
</table>
Thanks in advance!
EDIT:
I worked it out
to add project set the attributes like this: project.clientValue(self.selectedClient().Value);
then to reference them in view call it as a function: clientValue()
credit goes to Chris Patt for explaining that observables should be called as functions when you want to get their value
Because you're setting the ProjectWorkedOn observables and getting the selected values incorrectly. To set an observable you pass the value as a parameter as you would for a function and to get the value of an observable, you call it like a function. (They are in fact functions.):
self.addProjectWorkedOn = function () {
var project = new ProjectWorkedOn();
project.client(self.selectedClient());
project.job(self.selectedJob());
project.project(self.selectedProject());
project.workType(self.selectedWorkType());
self.ListProjectsWorkedOn.push(project)
}
EDIT
I was thinking you literally wanted the selected values, not the actual whole JSON object. What you're attempting simply isn't possible. The value of the select has to be an integer or string -- it can't be a full object. Usually you would handle something like this by first simply returning a value/text list to populate the selects, where the value would be something like the id. Then, upon selection, you would issue an AJAX request to retrieve the object with that id.