hello i am trying to pass inline edited table value as a parameter to play routes can someone help me with the same
here is my html code
<table class="gradienttable">
<thead>
<tr>
<th>Task</th>
<th>TimeSheetdate</th>
<th>Hours</th>
<th>IsBilled</th>
<th>Work Place</th>
</tr>
</thead>
<tbody>
#for(element <- CurrentPage) {
<tr>
<td contenteditable="true" id="task1">#element.getTask()</td>
<td>#element.getTimeSheetDate()</td>
<td contenteditable="true" id="hours">#element.getHours()</td>
<td contenteditable="true" id="isbilled">#element.getIsBilled()</td>
<td contenteditable="true"id="workplace">#element.getWorkPlace()</td>
<td>
</tr>
}
</tbody>
</table>
routes
GET /Application/edit controllers.Application.edit(task1:String)
application.java
public static Result edit(String task1)
{
return ok(DisplayTimeSheet.render(task1));
}
It looks like you're confusing the server-side rendered Scala template with the DOM actions of the client. For #{routes.Application.edit(task1.innerHTML)}, task1 doesn't exist as far as the non-DOM template is concerned.
Your use of <a href="..."> is kind of weird, because that would make a synchronous call and if you're into inline editing then maybe that's not what you want. This answer covers an asynchronous approach using Play's JavaScript router support (which, if you haven't seen it before, is very cool).
You'll need to expose edit in the JavaScript router:
// You can also return an F.Promise<Result>
public static Result jsRoutes() {
response().setContentType("text/javascript");
return ok(Routes.javascriptRouter("appRoutes", //appRoutes will be the JS object available in our view
routes.javascript.Application.edit()));
}
And then expose that method in the routes:
GET /assets/js/routes controllers.Application.jsRoutes()
That will generate a chunk of JavaScript that you can import
<script src="#controllers.routes.Application.jsRoutes()" type="text/javascript"></script>
And finally, write some JavaScript to handle the inline editing completion.
function doInlineEdit(taskName) {
appRoutes.controllers.Application.edit(taskName).ajax( {
success : function ( data ) {
target.closest('li').remove();
}
});
}
Then you just need to wire that method to be called when your inline-editable element changes content.
You can find additional info here.
Related
I'm using Blazor Sever to make a website.
And there's a need that i should convert my razor page to htmlpage.So taht i can use my HTMLtoPDF interface to let customers to download their project.DO you have any ideas?
Such as the following Razor page.There're many variables and custom components in this page,how can i convert it to a static HTML page? Thanks a lot !!!
<div id="ABS" class="text-left">
<table style="border:black solid;font-weight:bold;font-size:12px" width="1200" cellspacing="0" cellpadding="0" border="1">
<tr class="text-center" style="background-color: #99CCFF">
<td width="300">描述 <br> Description </td>
<td width="500">标准选项 <br> Standard Option</td>
<td width="400">备注 <br> Comments</td>
</tr>
<ABS_HU ABSTI_HU="#ABSTI.HU" IsExpended="#IsExpendAll" />
<ABS_WSS ABSTI_WSS="#ABSTI.WSS" IsExpended="#IsExpendAll" />
<ABS_SYSTEM ABSTI_System="#ABSTI.System" IsExpended="#IsExpendAll" />
<tr>
<td style="background-color: #CCFFFF" width="300" class="text-center">
</td>
<td width="500" class="text-center">
</td>
<td style="background-color: #CCFFFF" width="400">
</td>
</tr>
</table>
</div>
I assume the customer sees the report first and then click a button like "save."
The general idea is to let the client (browser) render your component, send the "raw HTML" back to the server, and then you can use your HTMLtoPDF.
Let's create the javascript function. Maybe there are ways to do it with vanilla js as well. You could add these lines to your _Host.cshtml file.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" ></script>
<script type="text/javascript">
window.copyHtml = function () {
return $("html").html();
};
</script>
In your Blazor component, inject IJSRuntime and call the method when the button is clicked.
#inject IJSRuntime js
#* all the other components here *#
<button class="btn btn-primary" #onclick="GenerateHtml">Save Report</button>
#code {
private async Task GenerateHtml()
{
String result = await js.InvokeAsync<String>("copyHtml", Array.Empty<Object>());
//in a wasm project, use HttpClient to send the data to the API
//as a "proof," it is sent back to the server
Console.WriteLine(result);
//send to HTMLtoPDF
}
}
In case the saving should be done without clicking a button, the Blazor component lifecycle can help. In the lifecycle of a component, when the rendering is finished, the method OnAfterRenderAsync is executed. So, you can override it and generate the HTML there.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if(firstRender == true)
{
await GenerateHtml();
}
}
I have the following view that displays gaming related data from a controller.
When the page initially loads, it hits an Index Controller that just lists all the gaming sessions ever created (100 total).
However, there is an input field, where the user can input a date, and then click a button.
When clicked, this button sends the date & time to another method called GamingSessionsByDate.
The GamingSessionsByDate method then returns new data which only contains Gaming Sessions with a start date of whatever the user entered.
Here is the view:
#model IEnumerable<GamingSessions.Session>
#{
ViewData["Title"] = "GamingSessionsByDate";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Gaming Sessions By Date</h2>
<input type="date" name="gameSession" id="gameSession">
<input type="Submit" id="postToController" name="postToController" Value="Find" />
#section Scripts
{
<script type="text/javascript">
$("#postToController").click(function () {
var url = '#Url.Action("GamingSessionsByDate", "GameSession")';
var inputDate = new Date('2019-01-23T15:30').toISOString();
$.ajax({
type: "POST",
url: url,
data: "startdate=" + inputDate,
success: function (data) {
console.log("data: ", data);
}
});
});
</script>
}
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.GameName)
</td>
<td>
#Html.DisplayFor(modelItem => item.PlayDuration)
</td>
</tr>
}
</tbody>
</table>
Here is the controller that returns the gaming sessions by date:
public IActionResult GamingSessionsByDate(DateTime startdate)
{
var response = GetGameSessionsList(startdate);
var r = response.Results;
return View(r);
}
By the way, I have hard-coded a date time value into the AJAX call above that I know contains 5 gaming sessions.
Please note that I am writing out the data returned from the controller in the AJAX success method.
So when I click the button, nothing happens on the screen, I just see the initially-loaded 100 gaming sessions from the call to the Index controller.
However, behind the scenes, I can see the 5 gaming sessions I need being written to the console via the console.log command in the Ajax call.
I also see the correct data when I step-through the project in Visual Studio.
So it looks like everything is working, but it appears as if the view/page is not getting refreshed.
So, how do I get that data to display on the page?
Thanks!
The XMLHttpRequest object in JavaScript (what actually makes "AJAX" requests) is what's known as a "thin client". Your web browser is a "thick client", it does more than just make requests and receives responses: it actually does stuff automatically such as take HTML, CSS, and JavaScript that's returned and "runs" them, building a DOM and rendering pretty pictures and text to your screen. A thin client, conversely, literally just makes requests and receives responses. That's it. It doesn't do anything on its own. You are responsible, as the developer, for using the responses to actually do something.
In the case here, that means taking the response you receive and manipulating the DOM to replace the list of game sessions with the different game sessions retrieved. How you do that depends on what exactly you're returning as a response from your AJAX call. It could be HTML ready to be inserted or some sort of object like JSON. In the former case, you'd literally just select some parent element in the DOM and then replace its innerHTML with the response you received. In latter case, you'd need to use the JSON data to actually build and insert elements into the DOM.
Returning straight HTML is easier, but it's also less flexible. Returning JSON gives you ultimate freedom, but it's more difficult out of the box to manipulate the DOM to display that data. That's generally the point where you want to employ a client-side framework like Vue, Angular, React, etc. All of these can create templated components. With that, you need only change the underlying data source (i.e. set the data to the JSON that was returned), and the component will react accordingly, manipulating the DOM as necessary to create the view.
I personally like to use Vue, since it has the least friction to get started with an it's almost stupidly simple to use. For example:
<div id="App">
<input type="date" v-model="startDate" />
<button type="button" v-on:click="filterGameSessionsByDate">Find</button>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td>{{ item.GameName }}</td>
<td>{{ item.PlayDuration }}</td>
</tr>
</tbody>
</table>
</div>
Then a bit of JS to wire it up:
(function (options) {
let vm = new Vue({
el: '#App",
data: {
items: options.items,
startDate: null
},
methods: {
filterGameSessionsByDate: function () {
let self = this;
$.ajax({
type: "POST",
url: options.filterByDateUrl,
data: "startdate=" + self.startDate,
success: function (data) {
self.items = data;
}
});
}
}
});
})(
#Html.Raw(Json.Encode(new {
items = Model,
filterByDateUrl = Url.Action("GamingSessionsByDate", "GameSession")
}))
)
That may look a little funky if you're not that used to JS. I'm just using what's called a closure here: defining and calling a function in place. It takes an options param, which is being filled by the parenthesis at the bottom. Inside those, I'm creating an anonymous object that holds info I need, such as the initial items to display and the URL to get filtered results from. Then, that object is encoded into JSON and dumped to the page.
There is no better way to frame the question but:
I have a button in my Application:
<div #displayDiv></div>
<button (click)="genTable()"></button>
The function call in the component is as follows:
#ViewChild('displayDiv') div: ElementRef;
.
.
genTable(): void {
this.div.nativeElement.innerHTML = ''; // clear the contents of the div
// Make some API call and get data in JSON format and store it
// in a private variable of the class called tableResult
// change content of div using innerHTML
this.div.nativeElement.innerHTML = `
<table>
<tr *ngFor="let eachProp of tableResult">
<th>{{eachProp}}</th>
</tr>
</table>
`;
}
The above code just shows {{eachProp}} as a string and not its content plus it is shown only once.
How do I make the *ngFor and the Angular templates viz. {{eachProp}} dynamically available when writing code within the innerHTML of the div?
Further Info
I already am rendering some Diagram in the displayDiv and once when the user clicks the button, the diagram is cleared off and a table is displayed based on the JSON response from a server.
JSON Response:
{"input":{"concept":"HighChair",
"parameters":["hasHeight","hasWidth"],
"filters":[{"min":3.0,"max":5.2}]},
"columns":["hasHeight","hasWidth"],
"rows":[["106.0","47.0"],["85.0","50.0"]]}
<div>
<table *ngIf="tableResult">
<tr>
<th *ngFor="let eachProp of tableResult?.columns">{{eachProp}}</th>
</tr>
<tr *ngFor="let row of tableResult?.rows">
<td *ngFor="let field of row">
{{field}}
</td>
</tr>
</table>
<div *ngIf="!tableResult">
<!-- put your initial diagram here -->
</div>
</div>
<button (click)="genTable()"></button>
And in the component, something like this:
/* typescript */
tableResult: any;
genTable(): void {
this.tableResult = this.someService.fetchResults();
}
There's no way to know from your code what's in tableResult but I suspect that's what's causing your error. A suggestion would be try to do {{ tableResult }} right below the table tag. That way you can see if its content is correct and if it is an array or collection that can be iterated by *ngFor.
** EDIT: As other users have pointed out, you are trying to insert an uncompiled element to the DOM. That's not going to fly. You need to have a template and do things in your template. see here: https://angular.io/guide/architecture#templates
I'm currently using razor to render some views in DotNetNuke and everything is working fine except for one thing I'm missing. I'm trying to get access to some module level methods such as EditUrl etc. but can't seem to figure out how to go about it.
This is what I have for my view, although it errors on EditUrl. I'm using RazorEngine.Render to render the view. There are some helpers that are including for basic DNN info but I can't seem to find anything like NavigateUrl or EditUrl.
Any ideas?
#inherits DotNetNuke.Web.Razor.DotNetNukeWebPage<dynamic>
<div id="items-panel">
<table>
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Items)
{
<tr>
<td>
#item.Title
</td>
</tr>
}
</tbody>
</table>
</div>
I found a way that seems to work for EditUrl by creating a helper method in the razor view shown below. It's a shame I have to do this because the ModuleInstanceContext is actually passed into the constructor of the RazorEngine but not exposed to the view. If anyone else finds a way around this I'd appreciate a comment.
#helper EditUrl(string keyName, string keyValue, string controlKey)
{
#DotNetNuke.Common.Globals.NavigateURL(Dnn.Tab.TabID, controlKey, "mid="+Dnn.Module.ModuleID, keyName + "=" + keyValue)
}
Edit: The following method also worked well for me. I added a property to the model named ItemViewUrl with a placeholder token for the ItemId and then just did a replace in the view.
In the calling page:
dynamic model = new ExpandoObject();
model.Items = ItemRepository.List();
model.ViewItemUrl = EditUrl("ItemId", "[ITEMID]", "ViewItem");
and then in the Razor view:
<td>#item.Title</td>
So I have a bunch of paragraph elements which are dynamically populated from a db. I have made the elements contenteditable. I now want to submit edits back the the db via a standard form submission. Is there a way to post the contenteditable elements back?
You have to use javascript one way or the other, it won't work as a "standard" form element as it would with a textarea or the like. If you like, you could make a hidden textarea within your form, and in the form's onsubmit function copy the innerHTML of the contenteditable to the textarea's value. Alternatively you could use ajax/xmlHttpRqeuest to submit the stuff a bit more manually.
function copyContent () {
document.getElementById("hiddenTextarea").value =
document.getElementById("myContentEditable").innerHTML;
return true;
}
<form action='whatever' onsubmit='return copyContent()'>...
If anyone is interested I patched up a solution with VueJS for a similar problem. In my case I have:
<h2 #focusout="updateMainMessage" v-html="mainMessage" contenteditable="true"></h2>
<textarea class="d-none" name="gift[main_message]" :value="mainMessage"></textarea>
In "data" you can set a default value for mainMessage, and in methods I have:
methods: {
updateMainMessage: function(e) {
this.mainMessage = e.target.innerText;
}
}
"d-none" is a Boostrap 4 class for display none.
Simple as that, and then you can get the value of the contenteditable field inside "gift[main_message]" during a normal form submit for example, no AJAX required. I'm not interested in formatting, therefore "innerText" works better than "innerHTML" for me.
Does it NEED to be standard form submission? If you cannot or do not want use a form with inputs, you may try AJAX (XMLHttpRequest + FormData), through which you could perform asynchronous requests and control better how response shows up.
If you want it even simpler, try jQuery's $.ajax function (also $.get and $.post). It sends data using simple JS objects.
Made a fully working example based on Rob's idea:
After hitting submit, the (hidden) textarea is updated with the table-data, in JSON-format.
(return true to submit)
function copyContent() {
const table = [];
$("tr").each(function() {
const row = [];
$("th, td", this).each(function() {
row.push($(this).text());
});
table.push(row);
});
$("#rows").val(JSON.stringify(table));
// return true to submit
return false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form onsubmit='return copyContent()'>
<textarea name="rows" id="rows" rows="4"></textarea>
<button type="submit">Submit</button>
</form>
<table class="table table-striped">
<thead>
<tr>
<th>Head 1</th>
<th>Head 2</th>
</tr>
</thead>
<tbody>
<tr>
<td contenteditable="true">edit </td>
<td contenteditable="true">me</td>
</tr>
<tr>
<td contenteditable="true">please</td>
<td contenteditable="true">😊</td>
</tr>
</tbody>
</table>