How do I add an Event Listener to a Dynamic Element? - dart

I have the following HTML:
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Item 1</td>
<td>Description of Item 1</td>
<td>
Edit
Delete
</td>
</tr>
<tr>
<td>2</td>
<td>Item 2</td>
<td>Description of Item 2</td>
<td>
Edit
Delete
</td>
</tr>
</tbody>
</table>
The table rows (tr elements) are added dynamically.
I wire up a click event to all Edit links like this:
void wireUpTableEvents() {
var editLinks = queryAll('#order-items table tbody [data-action="edit"]');
editLinks.forEach((element) {
element.on.click.add((event){
print(element.attributes['data-item-id']);
});
});
}
As said above, the table rows (tr elements) are added dynamically so the above code only works if I call wireUpEvents after I execute the method which adds the rows.
Does anyone know the syntax or adding a event listener to elements using DART's on.click.add() when the elements are dynamcially added in the future?
I tried checking the DART documentation but the documentation on Event Listeners is blank.
If I would be using jQuery I could be using something similar to:
$("#order-items table")on("click", "tbody [data-action="edit"]", function(){...})
...but I want to write my sample app only using DART.
Edit
Though future sounds great for callbacks it seemed slightly overkill for what I needed as there is no long running task in my scenario.
The closest I was able to get to attach my event listener to a static element but processing the click events of future sub-elements was this:
void wireUpTableEvents() {
var tableBody = query('#order-items table tbody');
// Attach Event Listener to the static tbody, which always exists.
tableBody.on.click.add((event) {
var clickedElement = event.srcElement;
var itemId = clickedElement.attributes['data-item-id'];
// Check if the clicked element was either one of the edit links or one of the delete links.
switch (clickedElement.attributes['data-action']) {
case 'edit':
// Replace print with calling a method to process edit request for this item.
print('processing edit click from item with id: $itemId');
break;
case 'delete':
// Replace print with calling a method to process delete request for this item.
print('processing delete click from item with id: $itemId');
break;
}
});
}​
The above code can execute before any of the actual tr elements are loaded and still works after the tr elements are loaded at some unknown later stage.
I also found that it now covers any dynamically added row, pre-loaded ones as well as other dynamically added ones for new records etc.

It sounds like you need to use Dart's Future object. John Evans has a recent post that gives an excellent overview. I'll try to give a simple example:
Let's say I have a class called htmlInDart which I call as follows:
void main() {
var htmlExample = new HtmlInDart().createStyles();
htmlExample
..then((htmlExample) => htmlExample.buildPage())
..then((htmlExample) => htmlExample.addListeners());
}
The class might look something like this:
class htmlInDart {
htmlInDart();
Future<htmlInDart> createStyles() {
final c = new Completer();
// create some styles
c.complete(this);
return c.future;
}
Future<htmlInDart> buildPage() {
final c = new Completer();
// build the page
c.complete(this);
return c.future;
}
Future<htmlInDart> addListeners() {
final c = new Completer();
// add some listeners
c.complete(this);
return c.future;
}
Hopefully this gives you some idea of how to implement it for your case.

Is there any reason you can't add the callback when you are adding the rows in the first place? Something like:
void addRow(TableElement table, ...) {
TableRowElement item = new TableRowElement();
...
table.nodes.add(item);
item.on.click.add((callback) { });
}

Related

You cannot apply bindings multiple times to the same element knockout

I know this question was asked before but using cleanNode did not fix the problem .
I need to display a list of orders Ids that are stored in an array .here is what I did so far:
self.Orders = ko.observableArray([]);
for (var i = 0; i <= self.OrdersIds().length; i++) {
//ko.cleanNode($("#Table"));
ko.applyBindings({ Orders: [{ orderId : self.OrdersIds()[i] }] }, document.getElementById("oTable"));
}
<table id="commentsTable">
<tbody data-bind="foreach: Orders">
<tr>
<td data-bind="text: orderIdt"></td>
</tr>
</tbody>
</table>
When u are doing ko.applyBindings without passing element id model automatically get applied to the whole document.
You can pass to function applyBindings second argument - html element to which you want to apply bindings. In your case :
ko.applyBindings({ Orders: this.Orders }, document.getElementById('oTable'));
That would map each property (recursive) in your model (Orders) to html table 'oTable'. And after that you can modify your Orders js model (add/remove items from array) and that all will be reflected in your html.
Your example is not fully correct, because there u neglect main advantage of knockout (i mean observables). When you do create your model in such way Orders: [{ orderId : self.OrdersIds()[i] } - it's not longer act as observable array and HTML can't react to model changes.

Track user clicks from list populated of URL's using MVC framework

I have an MVC 5 application that displays a list of 100+ unique URL's. The user can sort, search, and eventually click the URL from the list launching the website in a new window. I want to be able to track every time someone clicks a URL from that list. The URL list is populating as expected. View code below:
<table class="table table-condensed table-hover table-responsive table-striped">
<tr>
<th class="col-md-1">
#Html.ActionLink(" Name", "Index", new { sortOrder = ViewBag.ReportNameSortParam, SelectedReportCategory = ViewBag.SelectedReportCategory, FindReportName = ViewBag.FindReportName}, new { #class = "glyphicon glyphicon-sort", #title = "sort by name" })
</th>
</tr>
#foreach (var item in Model)
{
if (ViewBag.counter == "")
{
ViewBag.rowType = "normalRow";
ViewBag.counter = "1";
}
else {
ViewBag.rowType = "alternateRow";
ViewBag.counter = "";
}
<tr class=#ViewBag.rowType>
<td class="col-md-1" title="#Html.DisplayFor(modelItem => item.Report_Description) : #Html.DisplayFor(modelItem => item.SLA)">
#Html.DisplayFor(modelItem => item.Report_Name)
</td>
</tr>
}
</table>
What would be the best approach to track a user click when they are doing this via the View? I have a column in my table called 'Hits' that I would like to increment by 1 whenever that URL gets clicked. Ideally, after a user clicks a URL, I would like for the list to stay where it is and not have to refresh by hitting another controller and then repopulating. How can I write to the database on via the View?
as mentioned... capture the click event of each link clicked and perform an Ajax post to the relevant controller and action.
$(document).ready(function () {
$(document).on('click', 'a', function () {
var linkID = $(this).attr("id");
$.ajax({
type: "POST",
url: "#Url.Action("YourAction", "YourController")",
data:
{
linkid: linkID
}
});
});
});
hope that helps.
Consider standing up an action method that receives the URL and redirects to the final destination:
#Html.DisplayFor(modelItem => item.Report_Name)
In the controller, add:
public ActionResult Redirect(int id, string url)
{
//Get record using ID, and update Hits column
//Redirect to the final URL
return Redirect(url);
}
Be aware that if your URL's use any special characters, some encoding may occur... that might mean you have to do some encoding on the client end potentially. It's just good to test that out to confirm.

MVC foreach statement

Im new with MVC.
I have a model called UAV.
│Callsign│NumDeliveries│Mileage│MaxVelocity│MinVelocity│
 Hawk61   37    96    20     10
 BURL14   2047     57     30     15
 OTTO93   82    72    25     10
in cshtml file, i made a table only using Callsign, NumDeliveries, Mileage.
<table class="UAV_table" id="UAV_table">
<tr>
<th>Callsign</th>
<th>NumDeliveries</th>
<th>Mileage</th>
</tr>
#foreach (UAV uav in Model.UAVs)
{
<tr onclick="click_row()">
<td onclick="click_row()">
#Html.DisplayFor(modelItem => uav.Callsign)
</td>
<td>
#Html.DisplayFor(modelItem => uav.NumDeliveries)
</td>
<td>
#Html.DisplayFor(modelItem => uav.Mileage)
</td>
</tr>
}
</table>
 so the table shows all datas for Callsign, NumDeliveries, Mileage.
what i want to do is, when i click the row of the table, i want to see only that correspond information.
#foreach (UAVs uavid in Model.uavs)
{
<p class="detail_title" id="detail_title">
UAV: # (#Html.DisplayFor(modelItem => uavid.MaxVelocity))
</p>
}
for example, using above line of code, if i click first row of that table(callsign = Hawk61), i want to see like UAV: # 20 (MaxVelocity for Hawk61). MaxVelocity is not in the table, so i need to get it from database.
But I have problem with showing data. If i use right above code, it has #foreach statement, it shows all the Hawk61, BURL14, OTTO93's MaxVelocity.
it shows me like
UAV:# 20
UAV:# 30
UAV:# 25
I need to see only what i selected. (just shows what i click, in this example, only need to show UAV:# 20 which is first row, Hawk61's MaxVelocity).
is there any way to get the data from database not using foreach statement?
Thank you.
Since the values of MaxVelocityand MinVelocity are populated, you can make use of data- attributes to store the values in the DOM and use jquery to display them. For example
#foreach (UAV uav in Model.UAVs)
{
<tr class="uavrow" data-maxvelocity="#uav.MaxVelocity" data-minvelocity="#MinVelocity">
<td>#Html.DisplayFor(modelItem => uav.Callsign)</td>
<td>#Html.DisplayFor(modelItem => uav.NumDeliveries)</td>
<td>#Html.DisplayFor(modelItem => uav.Mileage)</td>
</tr>
}
And include some elements to display the associated data when you click on the row
<div>
<div><span>Call Sign: </span><span id="callsign"></span>
<div><span>Max Velocity: </span><span id="maxvelocity"></span>
<div><span>Min Velocity: </span><span id="minvelocity"></span>
</div>
And the script
$('.uavrow').click(function) {
// Get the call sign for the td element
$('#callsign').text($(this).children('td').eq(0).text());
// Get the velocity from the data attributes
$('#maxvelocity').text($(this).data('maxvelocity'));
$('#minvelocity').text($(this).data('minvelocity'));
});
If however the value were not populated, or you have a large number of properties to display, then it may be better to make an ajax call to a method (passing the callsign) which returns a partial view containing the details
<div id="uavdetails"></div>
$('.uavrow').click(function) {
var callSign = $('#callsign').text($(this).children('td').eq(0).text());
var url = '#Url.Action("Details", "YourController")';
$('#uavdetails').load(url, { CallSign: callsign });
});
Controller
public ActionResult Details(string CallSign)
{
UAV uav = // Get the UAV base on the CallSign value
return PartialView(uav);
}
Actually you have all data that you need in there.
The only thing that you need is to show proper item by using JavaScript.
You need to add parameter to your function call here:
<tr onclick="click_row('#uav.Callsign')">
And also add css class here:
#foreach (UAVs uavid in Model.uavs)
{
<p class="detail_title #uavid.Callsign" id="detail_title" style="display=none;">
UAV: # (#Html.DisplayFor(modelItem => uavid.MaxVelocity))
</p>
}
And then add a bit of javascript:
<script>
function click_row(elClass){
var elements = document.getElementsByClassName("detail_title");
for (i = 0; i < x.length; i++) {
if(x[i].className.contains(elClass)){
x[i].style.display = 'block';
} else{
x[i].style.display = 'none';
}
}
};
<script/>

How can I use input in grid view?

I'm going to use a grid view. it contains a check box per row.
its for deleting row. for example when user checked some checkbooks and click on delete button checked rows have to delete.
Now How can I give checked rows ID's in my action ?
Do you have any ideas ?
Thanks.
OK. We assume that you want to retrieve a list of products and show them in a raw html grid.
First of all, arrange your view like this:
#model IEnumerable<MyPrj.Product>
// ... Other codes ...
<table id="tblGrid">
<tr>
<th>Delete</th>
<th>ProductName</th>
// ... Other Properties ...
</tr>
#foreach(var item in Model)
{
<tr id="tr#(item.ProductID)">
<td>
#Html.Raw("<input type='checkbox' id='chk#(item.ProductID)' onclick='chkChange(#chkID)' />");
</td>
<td>#item.ProductName</td>
// ... Other Properties ...
</tr>
}
</table>
<input type="button" id="btnDelete" value="Delete Selected Rows" onclick="performDelete()"/>
Now, you have your view and it will render the grid for you with the supplied model. Then, you need to add some javascript and jquery codes to perform the row deletion for you.
A function to handle checkbox clicks:
<script>
function chkChange(id) {
if ($(id).val() != 'false')
$(id).val('false');
else
$(id).val('true');
}
// ...
</script>
And finally, a function to handle delete button clicks:
function performDelete() {
var rows = $("input:checked");
rows.each(function () {
$(this).parent().parent().remove();
});
}
That's it! - you're done. The complete <script> blog is like the following:
<script>
function chkChange(id) {
if ($(id).val() != 'false')
$(id).val('false');
else
$(id).val('true');
}
function performDelete() {
var rows = $("input:checked");
rows.each(function () {
$(this).parent().parent().remove();
});
}
</script>
You could place the grid along with its checkboxes inside an HTML form and make the Delete button as submit button for this form. Then when the button is clicked, the form will be submitted and the values of the selected checkboxes will be sent to the controller action so that you could delete the corresponding records.

Knockout automapped array with formatted (computed?) column

I have a CRUD single page using Knockout, everything works OK, I get the data from a JSON call, it fills an automapped observable array with the list of objects.
I can add or edit single items in that array.
The issue comes with formatting a currency (numeric) column I show in the table with the list of objects. I have tried using js functions, in a lot of ways but the formatted currency amount of the table does not update when I update an item.
If I use a binding for formatting the field then I can't edit it because it converts into a String.
What I need is a one-way binded (auto updated) formatted column for my currency column. But I can't create a computed column right away because I'm using an automapped array of objects. I tried adding the computed using the example in http://knockoutjs.com/documentation/plugins-mapping.html, but I don't know how to use it with an mapped array.
My viewmodel is something like this:
//--Accounts - Viewmodel Knockout
function accountViewModel() {
var self = this;
self.accounts = ko.observableArray(); //this is the list of objects
self.account = ko.observable(); //single item for creating or editing
//--get list------
self.getAccounts = function () {
$.postJSON(appURL + 'Accounts/GetList', function (data) {
ko.mapping.fromJS(data.Records, {}, self.accounts);
});
};
self.getAccounts();
}
Each account item have fields like:
-Id
-Name
-Balance <-- this is the column I want to format
Using it in the page:
<table data-bind="visible: accounts().length > 0">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Name</th>
<th scope="col">Balance</th>
</tr>
</thead>
<tbody id="accounts" data-bind="foreach: accounts">
<tr>
<td><span data-bind="text: Id"></span></td>
<td></td>
<td style="text-align:right;">
<span data-bind="text: formatCurrency(Balance), css: { negative: Balance() < 0, positive: Balance() > 0 }"></span>
</td>
</tr>
</tbody>
</table>
formatCurrency is just a js function for formatting the number:
formatCurrency = function (value) {
debugger;
if (value!=undefined)
return "$" + withCommas(value().toFixed(2));
//had to use value() instead of value, because of toFixed
}
Thanks!
When you set the text to the return value of a function (text: formatCurrency(Balance)) it only runs once. It's not an observable, so it's value will never update again. What you need is a true-blue computed observable. In order to get that, you'll have to customize the mapping. So simply create a view model for your individual account object, and then map your returned data onto that.
var SingleAccountViewModel = function (account) {
var model = ko.mapping.fromJS(account);
model.FormattedBalance = ko.computed(function () {
return formattedCurrency(model.Balance);
});
return model;
}
Then when you get your data back from your AJAX response:
$.postJSON(appURL + 'Accounts/GetList', function (data) {
self.accounts($.map(data.Records, SingleAccountViewModel));
});
The jQuery.map method will iterate through the array and return a new array composed of the return values of the function passed. In this case, that's your view model, which will now have a FormattedBalance computed on it that you can bind to.

Resources