AngularJS : Calling controller events from directives - events

Suppose you need to write a directive that takes one attribute-parameter - name of the controller's event function. The directive does some processing and then fires a notification on that event handler, passing it some processing parameters.
What would be a recommended way to set up such attribute in the directive to avoid unnecessary overhead, like two-way binding?
So far I was only able to achieve this by using two-way binding as shown in the example below:
app.controller("testCtrl", function ($scope) {
$scope.onClickEvent = function (ctrlDown) {
alert(ctrlDown);
}
});
app.directive("customInput", function () {
return {
restrict: "E",
scope: {
onClickNotify: "=onClick",
},
template: "<input type='text' ng-click='onClick()' />",
replace: true,
transclude: false,
link: function (scope, element, attrs, controller) {
scope.onClick = function () {
if (typeof (scope.onClickNotify) == 'function') {
scope.onClickNotify(window.event.ctrlKey);
}
}
}
}
});
<custom-input on-click="onClickEvent" />
In the example above the directive uses "=" - bi-directional binding for the attribute, the only way I was able to make it work. What I cannot understand is why can't we use "&"? According to AngularJS documentation, we should be able to, while passing parameters with names, i.e. like this:
scope.onClickNotify({ctrlDown: window.event.ctrlKey});
But it just doesn't work. And if I try and specify parameters in the event name like this:
<custom-input on-click="onClickEvent(ctrlDown)" />
then it still doesn't work. Instead, the value I pass in the directive is ignored.
I'm confused as to what is really going on in the background and why doesn't it work with "&" as expected? Two-way binding does look like an overhead to me in this scenario, because we are just passing the function name in one direction, if not as a simple attribute (with "#").
And if I'm doing something wrong here, then what is the right way?

I got it working using &. You are right. Creating a two-way binding is an overhead.
Actually you 're supposed to pass an object while calling the controller method:
app.directive("customInput", function ($timeout) {
return {
restrict: "E",
scope: {
onClickNotify: "&onClick",
},
template: "<input type='text' ng-click='onClick()' />",
replace: true,
transclude: false,
link: function (scope, element, attrs, controller) {
scope.onClick = function () {
if (typeof (scope.onClickNotify) == 'function') {
scope.onClickNotify({ctrlDown:"foo"});
}
}
}
}
});
HTML:
<custom-input on-click="onClickEvent(ctrlDown)" />

Related

Asp.NET MCV how to call a viewmodel function that performs a db connection on button click correctly?

I have a few viewmodels, which contains a few functions like:
public override string ToString()
{
return $"{M_IV_ID} {M_IV_INVOICEAMOUNT} {M_IV_MANDANT} {M_IV_TOTALAMOUNT} {M_IV_VEHICLE_PURCHASE} {M_IV_URGENT}";
}
And update / insert functionalities. All of them work. I wrote them separately from my Asp.NET project and tested them since I wanted to do my back end first.
Now I tried to use those functions in my Asp.NET application:
First I get Data from the db in my Controller:
M_IV_INVOICE invoice = QueryBuilder.GetFirstOrDefault(new M_IV_INVOICE(), #"WHERE M_IV_ID IN (75693)");
And pass it to my view:
public ActionResult Index()
{
return View(invoice);
}
Now I tried to use my toString() method just to see if it works:
<form id="formUpdate" runat="server">
<div>
<asp:button id="Button" class="btn btn-primary" runat="server">Update</asp:button>
</div>
</form>
#{
string MyFunction()
{
return Model.ToString();
}
}
#section Scripts
{
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
var button = document.getElementById('Button');
button.addEventListener('click', function () {
var test = "#MyFunction()";
console.log(test);
});
});
</script>
}
And if I do this, it works, and I get my model values printed in the browser console.
If I try to use my Update function instead of the toString() method:
public bool Update()
{
using (IDbConnection conn = ConnectionManager.GetConnection())
{
try
{
conn.Query(QueryBuilder.BuildUpdateForEntity(this, string.Format(" WHERE {0} = {1}", GetKey().Item1, GetKey().Item2)));
return true;
}catch(Exception e)
{
// TODO: Exceptionhandling
}
}
return false;
}
I get a Ora Exception right when the page loads, no button click performed:
Oracle.DataAccess.Client.OracleException: ORA-00936: missing expression
I struggle with understanding why, though.
The select I perform in my controller to get the data in the first place is working just fine, and as I said, if I print my model on button click, it works too.
What am I missing?
Edit:
It seems like the function is directly called while the eventhandler is added and since it has no data at the time, the ORA error is occurring.
But why? I don't understand this behavior.
Another Edit:
It has indeed something to do with the model isn't ready or something:
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
var button = document.getElementById('Button');
button.addEventListener('click', function () {
setTimeout(() => { console.log("#MyFunction()") }, 3000);
});
});
</script>
I added the delay and no error on loading the page.
But that would be just a band aid, not a solution.
If someone has an idea how to prevent that behavior, I would appreciate it.
The #{}symbol is used to write server-side C# or VB code with HTML code, so in the javascript, when you use the #MyFunction() function, the Myfunction will execute. You could set a break point in the MyFunction and the button click event to check it, no matter using the ToString() method or the Update() method, they will work when the DOMContent Loaded. So, when use the Update() method, you will meet the missing expression error.
Based on your description, you want to do some action (call the server-side function/method) when user clicks the button, right? If that is the case, I suggest in the controller you can add the Myfunction and Update action methods, then, in the button click event, you can use JQuery get(), post() or Ajax method to call the action method, refer the following code:
Controller:
[HttpPost]
public JsonResult AjaxMethod(string name)
{
PersonModel person = new PersonModel
{
Name = name,
DateTime = DateTime.Now.ToString()
};
return Json(person);
}
and View Page:
<input type="text" id="txtName" />
<input type="button" id="btnGet" value="Get Current Time" />
#section Scripts{
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script>
$(function () {
$("#btnGet").click(function () {
$.ajax({
type: "POST",
url: "/Test/AjaxMethod",
data: { name: $("#txtName").val() },
success: function (response) {
alert("Hello: " + response.name + " .\nCurrent Date and Time: " + response.dateTime);
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
});
});
</script>
}

Return a warning (not an error) via Ajax remote validation

I have an MVC Ajax callback that checks to see if a user input is valid. This callback is invoked via the [Remote] attribute on the associated model property.
I've changed my design, and I've decided that I would really like to warn the user if the value is incorrect, but I don't want the incorrect value to prevent model validation.
A quick search turns up several threads describing very involved solutions to the general problem of wiring up "unobtrusive warnings" similar to the "unobtrusive validation" magic baked into MVC (for example this SO post). I'm not looking for a general solution, and I don't want to spend a lot of time and energy on this, but I'm wondering if some Ajax guru knows of something I can return from the Ajax server routine that would have the effect of causing the unobtrusive validation client-side code to put up the message without triggering the validation error.
FYI, my existing server-side code looks like this:
public async Task<ActionResult> CouponCodeExists(string couponCode, int? planId)
{
if (some_logic) {
return Json("Coupon code is already taken", JsonRequestBehavior.AllowGet);
} else {
return Json(true, JsonRequestBehavior.AllowGet);
}
}
A RemoteAttribute is a validation attribute and triggering it will add a validation error to jQuery.validate.js and prevent the form from submitting. If you just want a warning message, and still allow the form to submit, you can just make your own ajax call in the inputs .change() event.
Assuming you have #Html.TextBoxFor(m => m.couponCode) in the view, add a placeholder for the message - say <div id="couponwarning">Coupon code is already taken</div> and style in as display: none;. Then add the following scripts
var url = '#Url.Action("CouponCodeExists")';
var warning = $('#couponwarning');
$('#couponCode').change(function() {
var code = $(this).val();
var id = .... // the value of your planId input?
$.get(url, { couponCode: code, planId: id }, function(response) {
if (response) {
warning.show();
}
});
});
$('#couponCode').keyup(function() {
warning.hide();
});
and the method can just return true to display the message, or null
if (some_logic) {
return Json(true, JsonRequestBehavior.AllowGet);
} else {
return Json(null, JsonRequestBehavior.AllowGet);
}

The parameter is not passed from the view to the action

Here is the dropdownlist which contains the parameter (selected value) that I would like to pass to the view:
<select id="sltBiblioSite" name="sltBiblioSite">
<option>Sélectionnez un BiblioSite</option>
#*Iterating BiblioSite Model*#
#foreach (var item in Model)
{
<option>
#item.NomSite
</option>
}
</select>
Here is where I tried different things (see commented code below) to pass the parameter to the fucntion:
var selectedBiblioSiteName;
$("#sltBiblioSite").change(function () {
//selectedBiblioSiteName = $("#sltBiblioSite").val();
//selectedBiblioSiteName = $("#sltBiblioSite").val().trim();
//selectedBiblioSiteName = $("#sltBiblioSite option:selected").text();
//selectedBiblioSiteName = $("#sltBiblioSite > option:selected").attr("value")
...
}
});
and here is one of the functions who is supposed to pass the parameter by calling the partial view.
function getReservationTable() {
$.ajax({
// Get Reservation PartialView
url: "/Home/ReservationsToPartialView",
type: 'Get',
data: { biblioSiteName: selectedBiblioSiteName },
success: function (data) {
$("#reservationDetailTable").empty().append(data);
},
error: function () {
alert("something seems wrong");
}
});
}
I tried to hardcode the parameter in the action and it's working.
The problem seems to be in the view but I don't know where, please help me to fix this problem.
public ActionResult ReservationsToPartialView(string biblioSiteNom)
{
//biblioSiteNom = "La feuille de chene";
var reservationByBiblio = ReservationLinq.GetReservationByBiblioSite(biblioSiteNom);
return PartialView("ReservationPV", reservationByBiblio);
}
I haven't done this in a long time, so I might be completely wrong but I think you are passing wrong parameter name. In your ajax request you have biblioSiteName and in your actions its biblioSiteNom.
Also try some alert before calling the ajax to see whether the read parameter biblioSiteName is correct.

Pass value from view to Ajax

Lest say I got a view with this loop:
#foreach (var item in Model.Blogposts)
{
#item.Id
#Html.JQueryUI().Datepicker("Date")
}
So I got an Id and also a date choosen from the datePicker. Can someone show me how I can pass these two values to an Ajax that the loads a method?
$(".setDate").click(function () {
})
.load("/Home/Method?id=" + $(this).data("#itemID"));
});
Am I on the right way with what i´ve done so far? Help appreciated. Thanks!
EDIT:
Here is the method to which I want to pass the values:
public ActionResult SetDate(string valuefromDatePicker, string itemId)
{
//Code that updates the Time-property
//This I can figure out
return RedirectToAction("Test");
}
Which should mean that the view should look something like this:
#Html.JQueryUI().Datepicker("Date", new { data_url = Url.Action("SetDate", "Home", new { id = item.Id }) })
Should I also add a submit-button and give it the class .setDate?
You seem to be using some custom #Html.JQueryUI().Datepicker extension method that is not part of ASP.NET MVC. You should have at least mentioned the custom library you are using. In general most of the jQuery UI helper methods have overloads with an htmlAttributes parameter. This would allow you to add custom data-* attributes that you could use in your javascript code later. Consult the documentation of the library you are using about how to specify custom HTML attributes but your code might look something along the lines of:
#foreach (var item in Model.Blogposts)
{
#item.Id
#Html.JQueryUI().Datepicker("Date", new { data_url = Url.Action("Method", "Controller", new { id = item.Id }) })
}
and then in your javascript file:
$('.setDate').click(function () {
var url = $(this).data('url');
$('#result').load(url);
});

Dynamically load Partial Views

How can i dynamically load a Partial View?
I mean I have this view, lets say ListProducts, there I select some dropdownlists with products, etc, and with the selected values from those I wanna fill a partial view, which would be in a div that was invisible but after onchange() event would become visible and with the data from the specific selected items.
Use jQuery's $.load() with a controller action that returns a partial view.
For example:
HTML
<script type="text/javascript">
$(document).ready(function()
{
$("#yourselect").onchange(function()
{
// Home is your controller, Index is your action name
$("#yourdiv").load("#Url.Action("Index","Home")", { 'id' : '123' },
function (response, status, xhr)
{
if (status == "error")
{
alert("An error occurred while loading the results.");
}
});
});
});
</script>
<div id="yourdiv">
</div>
Controller
public virtual ActionResult Index(string id)
{
var myModel = GetSomeData();
return Partial(myModel);
}
View
#model IEnumerable<YourObjects>
#if (Model == null || Model.Count() == 0)
{
<p>No results found</p>
}
else
{
<ul>
#foreach (YourObjects myobject in Model)
{
<li>#myObject.Name</li>
}
</ul>
}
You can do this by following these steps. In your controller, you return a partial view.
[HttpGet]
public virtual ActionResult LoadPartialViewDynamically()
{
var query = _repository.GetQuery();
return PartialView("_PartialViewName", query);
}
then in the view you have an empty div
<div id="partialgoeshere"></div>
and then load the partial view using jQuery:
function LoadPartialView() {
$.get("#Url.Action(MVC.ControllerName.LoadPartialViewDynamically())", { null }, function (data) {
$("#partialgoeshere").empty();
$("#partialgoeshere").html(data);
});
}
Hope this helps
I believe you can do something like this example, just using the change event on your dropdown instead. It's a simple jQuery call, you can find more on the jQuery website.
$("#dropdown").change(function() {
$("#destination").load("/Products/GetProduct", $(this).val(),
function(result) {
// do what you need to do
});
});
The first parameter is the view you need to call for the details.
The second parameter is the selected value.
The third parameter of the $.load function is the callback function, where you can parse the result and do what you need to do.
If you have a multiple select $(this).val() that will give you an array with the selected options.
If you want only return a Json object you may want to follow this example.
Use Ajax :)
http://api.jquery.com/jQuery.ajax/
Example:
$.post(window.gRootPath + "Customer/PartialView", { Question: questionId})
.done(function (data) {
$('#partialDiv').html(data.responceText);
});
You can use ajax to call action an then just insert html string using jQuery to the page where you want it to appear:
Server-side:
Render partial view to string
Renders partial view on server to html string, useful when you need to add partial view to ASP.NET MVC page via AJAX.
Client-side:
$('#yourDdl').change(function()
{
$.get('/InsertPartialViewUsingAjax', function (data)
{
$('#container').html(data);
});
});
The following article tells you how to do it with minimum javascript. Basically you return html instead of JSON to your response object.
https://www.simple-talk.com/content/article.aspx?article=2118

Resources