Allow to use to add an item to model asp.net mvc4 - javascript

The model:
public class oPage
{
public int PageId { get; set; }
public List<oContent> Contents { get; set; }
}
The oContent object have properties: Id and Text.
The view: (Razor)
#for (int i = 0; i <= Model.Agrees.Count; i++)
{
#Html.TextAreaFor(m => Model.Agrees[i].Text)
}
I want to let to user to add another content from the client.
I found some solutions that I don't like:
Write the html markup manually. I don't like it because maybe in one day the rendering's structure will change and my code will not word.
(Like here: http://www.techiesweb.net/asp-net-mvc3-dynamically-added-form-fields-model-binding/)
Get the structure from the server. I don't like this because.. well I'm always prefer not use the server unless I have to.
(Like here: How to add items to MVC model in javascript?)
I'm looking for a better and smarter solution.

The solution a little beat complex but I think that it cover all my points.
The logic is:
Get the html from the razor engine
#Html.TextAreaFor(m => Model.Agrees[i].Text).ToHtmlString()
Than replace the index number with the attributes name and id (It makes more sense than relying on all attribute value)
public static string CreateHelperTemplate(string PlaceholderPerfix, MvcHtmlString mvcHtmlString)
{
string Result = "";
XElement TempNode = XDocument.Parse(mvcHtmlString.ToHtmlString()).Root;
XAttribute AttrId = TempNode.Attribute("id");
AttrId.Value = AttrId.Value.Replace("0", PlaceholderPerfix);
XAttribute AttrName = TempNode.Attribute("name");
AttrName.Value = AttrName.Value.Replace("0", PlaceholderPerfix);
// Clear the content if exist
TempNode.SetValue(string.Empty);
Result = TempNode.ToString();
return Result;
}
The result it something like this:
<textarea class="form-control" cols="20" id="Perfix_$__Text" name="Agrees[$].Text" rows="2"></textarea>
Then, in javascript, when the user click on "Add item" button you can get the "template" and the index and replace the '$' with the index.
So, I not need to call the server and if the structure of the helper will change my code will still work.
It's working.

Related

Prevent HTML read-only text fields to be changed

I'm developing a Grails web application whose modules, mostly, must implement a master-detail interface. As an approach, I want to put the following code to your consideration:
Master Class:
import org.codehaus.groovy.grails.web.json.JSONArray
class MyForm {
String name
String address
String detail
BigDecimal total
static hasMany = [details: MyFormDetail]
static constraints = {
name()
address()
detail()
total()
}
static mapping = {
detail type: 'text'
}
def beforeInsert = {
def detailJSON = new JSONArray(detail)
detailJSON.each {
def quantity = it.getAt("quantity").toString().toBigDecimal()
def description = it.getAt("description").toString()
def unitaryPrice = it.getAt("unitaryPrice").toString().toBigDecimal()
def subtotal = it.getAt("subtotal").toString().toBigDecimal()
def myFormDetail = new MyFormDetail(
quantity: quantity,
description: description,
unitaryPrice: unitaryPrice,
subtotal: subtotal
)
this.addToDetails(myFormDetail)
}
}
}
Detail Class:
class MyFormDetail {
Integer quantity
String description
BigDecimal unitaryPrice
BigDecimal subtotal
static belongsTo = [myForm: MyForm]
static constraints = {
quantity()
description()
unitaryPrice()
subtotal()
}
}
myFormUtilities.js helper file:
$(document).ready(function() {
$("#detailTable").jqGrid({
datatype: "local",
height: 100,
colNames: ["QUANTITY","DESCRIPTION","UNIT. PRICE","SUBTOTAL"],
colModel:[
{name:'quantity',index:'quantity', width:100},
{name:'description',index:'description', width:400},
{name:'unitaryPrice',index:'unitaryPrice', width:100},
{name:'subtotal',index:'subtotal', width:100}],
caption: "DETAIL"
});
createTable();
$("#addRow").bind("click",addRow);
$("#removeRow").bind("click",removeRow);
$("#quantity, #unitaryPrice").bind("keyup",calculateTotal);
function calculateTotal(){
let quantity = parseFloat($("#quantity").val());
let unitaryPrice = parseFloat($("#unitaryPrice").val());
let subtotal = quantity*unitaryPrice;
$("#subtotal").val(subtotal);
}
function addRow(){
let row = new Object();
row.quantity = $("#quantity").val();
row.description = $("#description").val();
row.unitaryPrice = $("#unitaryPrice").val();
row.subtotal = $("#subtotal").val();
let detailJSON = ($("#detail").val()=="")?"[]":$("#detail").val();
let mydata = $.parseJSON(detailJSON);
mydata.push(row);
$("#detail").val(JSON.stringify(mydata));
createTable();
}
function removeRow(){
let filaId = parseInt($('#detailTable').jqGrid('getGridParam','selrow')) - 1;
let mydata = $.parseJSON($("#detail").val());
let nuevoMydata = new Array();
for(let i=0;i<mydata.length;i++){
if(filaId!=i)
nuevoMydata.push(mydata[i]);
}
$("#detail").val(JSON.stringify(nuevoMydata));
createTable();
}
function createTable(){
let total = 0;
let aRow = new Object();
let detailJSON = ($("#detail").val()=="")?"[]":$("#detail").val();
let mydata = $.parseJSON(detailJSON);
$("#detailTable").jqGrid("clearGridData", true);
for(let i=0;i<mydata.length;i++){
aRow = mydata[i];
total += parseFloat(aRow.subtotal);
$("#detailTable").jqGrid('addRowData',i+1,aRow);
}
$("#total").val(total);
}
});
This is the displayed form (I know it's an autogenerated view but see it as a very basic GUI mockup, please):
So, these are the issues:
Both Subtotal and Total fields are calculated fields that were set
read-only to prevent user to modify their content, but I found that
using the browser element inspector their content and properties
(like read-only) can be modified.
Same happens with detail. If its content is altered, a server side
error will be produced when the instance is saved because the beforeInsert block needs a valid
JSON string to create the detail instances. This field is also used to generate the detail JqGrid. This field will be
hidden.
Everything with this example is working as expected but a fault could be caused by a curious user.
Is there a way to prevent these fields text to be changed even using
the inspector?
Is there another place to keep this data safe from alterations?
I'm using this detail field to save a JSON string and this is my solution to implement the master-detail pattern. Is there another
way to improve this implementation avoiding the need to define a
field for this string?
If your users have access to the inspector/web console, then they have access to all the client-side data that you do (they are only limited by their knowledge of how to use the browser's dev tools). They can change any element's text or input's value. Thus, the only place that would be 100% safe to store this data is on the server side.
What you can try, which would at least make it a lot more work for them to update anything, and might prevent the change events calling the server, is to hide each input (with CSS for instance) and replace it in the visible HTML with a <span> element (or some other non-form element). Then make sure that whenever the <input> value is changed, you update the text of the corresponding span. Something like:
$("#subtotalSpan").html( $("#subtotal").val() );
added inside the calculateTotal function. If you want them to not even be able to edit the HTML of a span, you could go even further and draw the value to a <canvas> instead of a <span>. They won't be able to change text drawn into a canvas element.
Sorry, there is no easy answer for protecting the data sent from the browser. Any user-submitted input should be treated as dirty by your server.
Data sent in a POST or GET can always be altered on the client side or even created wholesale (with something like CURL or Postman). For that reason, you should always validate the user input on the server side and compute your totals there.
If you have properties that you don't want to participate in the mass property data binding you should configure them with bindable: false.
You have this:
class MyFormDetail {
Integer quantity
String description
BigDecimal unitaryPrice
BigDecimal subtotal
static belongsTo = [myForm: MyForm]
static constraints = {
quantity()
description()
unitaryPrice()
subtotal()
}
}
You could change that to something like this:
class MyFormDetail {
Integer quantity
String description
BigDecimal unitaryPrice
BigDecimal subtotal
static belongsTo = [myForm: MyForm]
static constraints = {
quantity()
description()
unitaryPrice bindable: false
subtotal bindable: false
}
}
If you have something like this:
MyFormDetail mfd = new MyFormDetail()
// The body of the request will be read and
// bound to mfd but subtotal and unitaryPrice
// will NOT be updated...
mfd.properties = request
If you do that, you can still assign values those properties directly:
mfd.subtotal = // ... some value here
Another option is to submit your form to a controller action which accepts a command object. That CO could look like this...
class FormCommand {
Integer quantity
String description
}
If you have an action that looks like this:
class SomeController {
def someAction(FormCommand co) {
// it won't matter if subtotal
// and unitaryPrice are in the
// request, the co only has the
// the properties you want so now
// you can use those to update the
// corresponding persistent entity
// in a safe way
}
}

Reading real-time value from HTML element in URL page using jQuery

I've got this website: https://coinyep.com/he/ex/ETH-ILS
Inside this URL web page, there is an input element, and its HTML id is: "coinyep-input2".
I need to read its value (= approximately 637.49 ILS) using jQuery in Visual Studio.
So far, I tried everything, no success.
Any help will be appreciated.
For example, in jQuery there is a command :
document.getElementById("coinyep-input2");
==> I need to do the same thing but to do it from the URL page in order to get values in real-time (exchange rates).
Does this help?
$("#coinyep-input2").attr("data-valuta");
or
$("#coinyep-input2").attr("data-value");
or
$("#coinyep-input2").val();
For now, I found solution in C# (server), using it by Razor on the client
[Based on RSS URL]:
public float getExchangeRate_ETH_To_ILS()
{
float exchangeRate;
string[] words;
int indexOfExchangeRate;
using (var client = new WebClient())
{
try
{
var htmlPage = client.DownloadString("https://coinyep.com/he/rss/ETH-ILS.xml");
words = htmlPage.Split(' ');
indexOfExchangeRate = words.IndexOf("ILS");
exchangeRate = (float)Convert.ToDouble(words[indexOfExchangeRate - 1]);
}
catch(Exception e)
{
exchangeRate = -1;
}
}
return exchangeRate;
}
in the client, I use this fucntion via model (Razor):
var cal1 = #Model.getExchangeRate_ETH_To_ILS();
But there is a risk: if the template of the RSS will be changed, we are in trouble.

get sort direction from jquery bootgrid in C#

i have setup a jquery bootgrid on a .net mvc web page, it shows me a grid with a few commands,like sorting,search autocomplete,scroll pages. Library is here: http://www.jquery-bootgrid.com/documentation
The grid works well, and i decided to send the commands through ajax to a function behind. The library then sends a list of strings to the function, used to handle the grid:
current 1
rowCount 10
sort[filename] asc
where filename is one of the columns for which i wanna sort. it could be sort[id], sort[name] or whatever i set my column to be.
the values are pretty clear, the ajax sends to the function the current grid page,the number of rows and the sorting direction.
but when i get in the function i can read only the first 2 values:
public ActionResult AjaxRequestData(string current,string rowCount,string sort)
this definition reads the first 2 values from the web page,but cannot read the sort ,because the actual name of the var is sort[filename], it's not an array of strings.if i either declare sort as string or string[],the result is always null.
How should i declare the variables in the action? So far i could read the sort by using formcollection["sort[filename]"],formcollection["sort[id]"] etc but i have a lot of columns and i really dont want to write out a condition for each one of them,is there any other solution to this?
Approch1.
consider you have table with columns "col1, col2, col3, ...".
you can use:
public ActionResult AjaxRequestData(string current,string rowCount,Sort sort){
//sort.col1 == 'asc' (consider sorted by col1 in ascending order)
}
public class Sort
{
public string col1 { get; set; }
public string col2 { get; set; }
public string col3 { get; set; }
//... other columns
}
Approach 2.
You can remove you parameters and parse data manually. (notice i used post here instead of get)
[HttpPost]
public object AjaxRequestData(){
string jsonContent = Request.Content.ReadAsStringAsync().Result;
Dictionary<string, string> keyvalues = new Dictionary<string, string>();
string[] keyvalue_strings = jsonContent.Split('&');
string sort_column = "";
string sort_direction = "";
for (var i = 0; i< keyvalue_strings.Length; i++)
{
var a = keyvalue_strings[i].Split('=');
a[0] = a[0].Replace("%5B", "[").Replace("%5D", "]");
keyvalues.Add(a[0], (a[1]));
if (a[0].Contains("sort"))
{
sort_column = a[0].Replace("sort[", "").Replace("]", "");
sort_direction = a[1];
}
}
//now you have keyvalues, sort_column, sort_direction.
//...
}

Javascript Removing elements from an array based on another array

I am using javascript with knockout.js and I have 2 arrays:
self.timesheets = ko.observableArray();
self.selectedTimesheets = ko.observableArray();
I load the timesheet data like so:
$.ajax({
url: '/api/Invoice/GetPendingTimesheets',
type: 'GET',
success: function (timesheetData) {
self.timesheets(timesheetData);
}
});
Where the timesheets are defined like so:
public class Timesheet
{
public int Id { get; set; }
public DateTime WeekStart { get; set; }
}
The selectedTimesheets array keeps track of which checkboxes have been selected
What I would like to do is remove the elements in self.timesheets that are also in self.selectedtimesheets, and I am having a mental block figuring out how to do this:
I know there should be something like
self.timesheets.remove(function (el) {
// ????
});
But I just can't think of how to do it exactly.
Programmatically, you want to iterate through your self.timesheets array and compare each item's id to the ids in self.selectedtimesheets. If the ids match, you want to remove that item from self.timesheets.
Or push all items from both arrays into one new array and remove duplicates.
But since you're using Knockout, if you run the compareArrays utility function:
var differences = ko.utils.compareArrays(self.timesheets, self.selectedtimesheets);
differences will now be an array of only the different values.

List<T> to Javascript array

I have the following classes defined
public ReportsViewmodel
{
public GeographicData GeographicData { get; set; }
...
}
public class GeographicData
{
public List<ZipcodeData> Regions { get; set; }
...
}
public class ZipcodeData
{
//TupleList is defined as public class TupleList<T1, T2> : List<Tuple<T1, T2>>
public TupleList<double, double> Boundries { get; set; }//Contains list of Lat/Long values for plotting on a map.
}
inside my view, I need to do something like this:
foreach (region in GeographicData.Regions)
foreach (boundry in region.Boundries)
add item1 & item2 to a 2 dimensional Javascript array
In the end, I want my javascript array to look like:
var vmBoundries= [[34.1, -85.4], [34.8, -85.234], [34.347, -85.345], [34.541, -85.434], [34.2341, -85.4]];
I can't figure out how to access the data from my view. I keep running into scope issue. For example, if I try to use a javascript for loop I can't index into my ViewModel lists because the loop variable is undefined when I call #Model.GeographicData.Regions[i]...
So how do I pull the data from my ViewModel into the Javascript array?
Typically you'd:
Transform the data into the format you want
Serialize the transformed data into JSON
Assign a JavaScript variable to the serialized value.
So, something like this:
#{
IEnumerable<double[]> flattened = vm.GeographicData.Regions
.SelectMany(region => region.Boundries
.Select(tpl => new double[] { tpl.Item1, tpl.Item2 }));
string json = new JavaScriptSerializer().Serialize(flattened);
}
<script type="text/javascript">
var arr = #json;
// do something with arr.
</script>
Something like:
var array = (from region in GeographicData.Regions
select from boundry in region.Boundries
select new object[] { boundry.Item1, boundry.Item2 }).ToArray();
This will give you a 2D array that you can then just serialize.
Example: https://dotnetfiddle.net/Y9KOaq
I would use a javascript serializer to make it easier:
using System.Web.Script.Serialization;
And add a helper method to ReportsViewmodel:
public string GetBoundriesJs()
{
var pairs =
from r in GeographicData.Regions
from b in r.Boundries
select new[] { b.Item1, b.Item2 };
return new JavaScriptSerializer().Serialize(pairs);
}
Then you can just call it where you need it in your view:
var vmBoundries = #Model.GetBoundriesJs();

Categories

Resources