How do you implement a generic CRUD grid using knockout.js? - model-view-controller

I have a very nice grid with Create, Retrieve, Edit and Delete functionality. I am using knockout.js on the client and WebAPI on the back end.
I would like to extend this to a number of different objects. Essentially I have a List<T> where T is an MVC view model and the knockout view and view model should be able to work out what they should look like based on T.
Does anyone know of a simple way to display any viewmodel (with specific controls for editing based on the viewmodel itself - eg datepicker for dates, input for string, drop downs etc) in a grid format and have generic CRUD functions.
Below is an example of the exiting HTML I am using (for a specific object):
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Value Date</th>
<th>Position Date</th>
<th>Book</th>
<th>Currency</th>
<th>Currency Base</th>
<th>Amount1</th>
<th>Amount2</th>
<th>Position Type</th>
<th style="width: 100px; text-align:right;" />
</tr>
</thead>
<tbody data-bind=" template:{name:templateToUse, foreach: pagedList }"></tbody>
</table>
and the templates look like this:
<script id="itemsTmpl" type="text/html">
<tr>
<td data-bind="text: valueDate.formattedDate"></td>
<td data-bind="text: positionDate.formattedDate"></td>
<td data-bind="text: book"></td>
<td data-bind="text: currency"></td>
<td data-bind="text: currencyBase"></td>
<td data-bind="text: amount1"></td>
<td data-bind="text: amount2"></td>
<td data-bind="text: positionType"></td>
<td class="buttons">
<a class="btn" data-bind="click: $root.edit" href="#" title="edit"><i class="icon-pencil"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-trash"></i></a>
</td>
</tr>
</script>
<script id="editTmpl" type="text/html">
<tr>
<td><input data-bind="datepicker: valueDate.formattedDate, datepickerOptions: { dateFormat: 'yy/mm/dd' }"/></td>
<td><input data-bind="datepicker: positionDate.formattedDate, datepickerOptions: { dateFormat: 'yy/mm/dd' }"/></td>
<td><input data-bind="value: book"/></td>
<td><input data-bind="value: currency"/></td>
<td><input data-bind="value: currencyBase"/></td>
<td><input data-bind="value: amount1"/></td>
<td><input data-bind="value: amount2"/></td>
<td><input data-bind="value: positionType"/></td>
<td class="buttons">
<a class="btn btn-success" data-bind="click: $root.save" href="#" title="save"><i class="icon-ok"></i></a>
<a class="btn" data-bind="click: $root.cancel" href="#" title="cancel"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
I would like the View as well as the View Model to be generic and not "hard-coded" as above. I am sure someone else must have done something like this.

One solution would be that:
AJAX call gets a list of JSON viewmodels
If list is empty then do not display anything
If list has items, pick first item and go through properties
Start constructing the view on the client by looping through proprties
Append the string as a DOM element to the main DIV
Wireup knockout
There are problems with this approach. What if a property is null for the first object but exists in another? If so we do not setup the element for it.
A better option is to use content negotiation to get a dedicated template:
So GET /api/customers will return customers but if you request text/knockout-template+html then you get back a knockout template as string and then you append to DIV and wireup knockout. So server can generate template using reflection or customise for some models.

Related

Form is empty when submitted using Thymeleaf

I want to fill a list of applications for a university in the frontend. Each entry is supposed to hold two buttons: one for accepting the application and one for rejecting it. I created one form for each submit-button each.
<div class="container">
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Firstname</th>
<th scope="col">Lastname</th>
<th scope="col">Grade</th>
<th scope="col">NC</th>
<th scope="col">Course</th>
<th scope="col">Certificate</th>
<th scope="col">Recommendation</th>
<th scope="col">Decision</th>
</tr>
</thead>
<tbody>
<tr th:each="applicationOpen, rowStat: ${lstOpApplications}">
<th th:text="${rowStat.count}">1</th>
<td th:text="${applicationOpen.firstname}">firstname</td>
<td th:text="${applicationOpen.lastname}">lastname</td>
<td th:text="${applicationOpen.highschool_grade}">grade</td>
<td th:text="${applicationOpen.nc}">nc</td>
<td th:text="${applicationOpen.name}">coursename</td>
<td th:text="${applicationOpen.highschool_certificate}">certificate</td>
<td th:text="${applicationOpen.document}">recommendation</td>
<td>
<form action="#" th:action="#{/Bewerberubersicht}" th:object="${decisionForm}" method="post">
<input type="hidden" name="application_id" th:field="*{application_id}" value=${applicationOpen.id}"/>
<input type="hidden" name="decision" th:field="*{decision}" value=1/>
<button type="submit" class="btn btn-success"><i class="fas fa-edit"></i>Accept</button>
</form>
<form action="#" th:action="#{/Bewerberubersicht}" th:object="${decisionForm}" method="post">
<input type="hidden" name="application_id" th:field="*{application_id}" value=${applicationOpen.id}/>
<input type="hidden" name="decision" th:field="*{decision}" value=2/>
<button type="submit" class="btn btn-danger"><i class="fas fa-edit"></i>Reject</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
When I look in the Browser's Dev-Tools I can see that the content of the POST-request is
application_id: ""
decision: ""
When I replace value=${applicationOpen.id} with e.g. value=5 it is still empty. Hence, it should not be a problem with applicantOpen. Also, the list in the frontend is being filled just fine, so all of that should work. I first thought is is a problem with the DecisionForm class, but it seems my subsequent problems are caused by the issue described here.
The th:field attribute overrides content of name and value attributes. The decisionForm object seems to have empty fields so all forms have empty values.
Basically using th:object with th:field is convenient when you need to have your form prepopulated with values. It establishes initial state of your form, not the target. Using them makes sense for single form, not for multiple forms in loop with varying values.
In your case: please remove both th:object and th:field attributes. Instead use value="1" or value="2" for decision input, and th:value="${applicationOpen.id}" for application_id input.

Thymeleaf binding a selected value from a table

I'm having a table with multiple elements that are generated from a List and every table element from that list has a button.By clicking that button there is a post submit request which should bind the values from that table data element to a #ModelAttribute object in spring boot.
The problem is that i'm able to map the whole list but I want to bind only the table element where the button was pressed.
<div class="table-responsive">
<form th:action="#{/saveAd}" th:object="${object}" method="POST">
<table class="table table-sm table-hover">
<thead class="thead-dark">
<tr>
<th>Image</th>
<th>Title</th>
<!-- <th style="width: 16.66%">Link</th> -->
<th>Price</th>
<th>City</th>
</tr>
</thead>
<tbody id="myTable">
<th:block th:each="element, itemStat: *{lista}">
<tr
th:onclick="'javascript:rowClicked(\'' + *{lista[__${itemStat.index}__].url} + '\');'">
<input type="hidden" readonly="readonly"
th:name="?"
th:value="${element.getUrl()}" />
<td>
<input type="hidden" readonly="readonly" th:name="img" th:value="${element.getImg()}" />
<img th:src="*{lista[__${itemStat.index}__].img}" class="size" name="img" />
</td>
<td>
<input type="hidden" readonly="readonly" th:name="?" th:value="${element.getTitle()}" />
<span th:text="*{lista[__${itemStat.index}__].title}"></span>
</td>
<td>
<input type="hidden" readonly="readonly" th:name="?" th:value="${element.getPrice()}" />
<span th:text="*{lista[__${itemStat.index}__].price}"></span>
</td>
<td>
<input type="hidden" readonly="readonly" th:name="?" th:value="${element.getCity()}" />
<span th:text="*{lista[__${itemStat.index}__].city}"></span>
</td>
<td><input class="btn btn-danger" type="submit"
value="Save"></td>
</tr>
</th:block>
</tbody>
</table>
</form>
</div>
The Controller:
#RequestMapping(path = "/saveAd", method = RequestMethod.POST)
public String saveAd(#ModelAttribute("Value") ListCreationAutovitDto listCreationAutovitDto) {
return "home";
}
I have a hidden input type for every td which should map the values but i've tried naming it in different ways but i can't make it work.Are there any ways to bind only the values where the button was pressed?
Idea 1: Make each td contain a form pointing to your controller method. Then bind the values you want to pass to the model by using hidden input fields in the form. Each button would then do a submit for the form it is in.
Idea 2: If you're mapping json to your java objects in your app, you can construct the json request body in JavaScript to contain only the stuff you want for that request

Can parsley.js work with knockout.js?

I just learned about parsley.js and I'm trying to add it's validation capabilities to my project that is already wired up with knockout.js bindings. Here is the markup:
<form id="form-add-gift" data-bind="submit: addGift" data-validate="parsley">
<table class="table table-striped">
<thead>
<tr>
<th>Gift</th><th>Description</th><th>Url</th><th></th>
</tr>
</thead>
<tfoot>
<tr>
<td><input id="txtName" name="txtName" type="text" data-required="true" data-bind="value: newGift().name" /></td>
<td><input id="txtDescription" name="txtDescription" type="text" data-bind="value: newGift().description" /></td>
<td><input id="txtUrl" name="txtUrl" type="text" data-type="url" data-bind="value: newGift().url" /></td>
<td><button id="btnAdd" name="btnAdd" class="btn" type="submit" data-bind="disable: newGift().name.length > 0">Add gift</button></td>
</tr>
</tfoot>
<tbody data-bind="foreach: gifts">
<tr>
<td id="tdName" data-bind="text: name"></td>
<td id="tdDescription" data-bind="text: description"></td>
<td id="tdUrl" data-bind="text: url"></td>
<td><a id="btnRemove" class="btn btn-danger" href="#" data-bind="disabled: $parent.isClaimed, click: $parent.removeGift">Remove</a></td>
</tr>
</tbody>
</table>
</form>
When I click the "Add gift" button, my knockout.js addGift() function fires and the parsley.js validation occurs afterward. Obviously this is incorrect. Is there any way to get parsley.js to play nice with the knockout.js bindings?
I don't think parsley.js meant to work directly with KnockoutJs, but this can't stop you from using them both nicely.
Quick look through the Documentation -> Javascript - > Form you can use this method:
$('#form').parsley('isValid');
Useful if you want to integrate the form validation process inside
custom functions, without triggering error messages.
UPDATE
you can try this too:
$( '#form' ).parsley( 'validate' );
Useful if you want to integrate the form validation process inside
custom functions.

how to determine xpaths for ajax element

I need to detemine xpath for element mainForm:queryConfigure:fetchReport.
<span id="mainForm:queryConfigure:j_id18">
<table id="mainForm:queryConfigure:j_id19"
class="showReportTable" align="center">
<tbody>
<tr>
<td>
<input id="mainForm:queryConfigure:fetchReport" type="image"
src="images/show_report.gif" name="mainForm:queryConfigure:fetchReport"/>
</td>
</tr>
</tbody>
</table>
</span>
I tried
selenium.click("//input[#id='mainForm:queryConfigure:fetchReport'][#type='image'][#src='images/show_report.gif']");
and
selenium.click("//input[#id='mainForm:queryConfigure:fetchReport']");
One more case:
<div class="tabUnselectedText" align="center">
Notifications
</div>
Id and name attribute values are acceptable locators for method click. See locating elements in the documentation.
selenium.click('mainForm:queryConfigure:fetchReport');

How to hide Amazon webstore default toolbar with Prototype?

after playing around this morning, i've found that there's this default chunk of html code in the amazon webstore which will add a toolbar on top of the page. the html looks like below:
<td id="wba_logo_bg">
<table class="logo" border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody><tr><td align="left"></td>
<td class="wba_account" style="padding: 5px;" align="right" valign="top">
<table border="0" cellpadding="0" cellspacing="0">
<form action="#" id="searchForm" method="get" name="searchForm"></form>
<tbody><tr><td class="wba_account_link">
<a xmlns:xhtml="http://www.w3.org/1999/xhtml" class="myAccountNav" href="#" onclick="return false;">home</a></td>
<td class="myAccountDots"></td>
<td class="wba_account_link"><a class="myAccountNav" href="#" onclick="return false;">view cart</a></td>
<td class="myAccountDots"></td><td class="wba_account_link"><a class="myAccountNav" href="#" onclick="return false;">my account</a></td>
<td class="myAccountDots"></td><td class="wba_account_link"><a class="myAccountNav" href="#" onclick="return false;">order status</a></td>
<td><img src="pageEditor_files/1_pixel.gif" hspace="7"></td>
<td><input name="keyword" tabindex="1" type="text"></td>
<td><img alt="Search" class="wba_search_btn" onclick="return false;" onkeyup="if (13==event.keyCode) searchForm.submit();"
src="pageEditor_files/btn_search.gif" style="cursor: pointer;" tabindex="2" title="Search" hspace="3">
</td></tr></tbody>
</table>
</td></tr></tbody>
</table>
</td>
and thus far i was able to use prototype to find those with the class name of wba_account_link and hide them via the codes below:
function hideAmazonToolbar()
{
$("#wba_logo_bg").hide();
}//end function
but what i really want to do is preferably to hide the whole tbody instead, but with my limited prototype skills, i don't really know how to do this. can anybody point me to the right resources on how to get this done?
EDIT
Went higher up and apparently there's a td with an id, and solve it using prototype hide function! man, i love javascript framework :)
You want the up function. I think this should do it.
document.getElementsByClassName('wba_account_link').up('tbody');

Resources