Create a very simple calculator widget

Topics: Customizing Orchard, General, Troubleshooting, Writing modules
Jul 13, 2011 at 3:21 PM

Hi, I wonder if anyone can help me. I have been trying to create a very basic widget that doesn't depend on a database.

The widget that I would like help on is a simple addition calculator which returns a result without refreshing the page (AJAX). I would be very greatful if someone could shed me some light on this!

Thanks in advance.

- Craig

Coordinator
Jul 13, 2011 at 7:45 PM

Well, if you have specific questions, we’d be happy to help. For general information on widgets, see: http://www.orchardproject.net/docs/Writing-a-widget.ashx. About Ajax, well, there is nothing special about doing it in Orchard, you’d just do it the same way as in ASP.NET MVC: create a controller action, return a JSON result from it, and on the client-side, invoke that action using jQuery.

Jul 14, 2011 at 9:29 AM

Well we are currently finding it very difficult to create a widget that doesnt depend on creating a new database table and store a record. The widget that we have in mind wouldnt have an editor view that stores a record. Only a normal view part for the widget which asks the user to enter two values (number 1 and number 2) which will be added up and return the total without refreshing the webpage. We have went through the tutorials in the Orchard documentation but still haven't found a solution for to do this. I'm sure it probably will take 10 min to create this if I knew how to.

 

- Craig   

Jul 14, 2011 at 9:29 AM
Edited Jul 14, 2011 at 9:30 AM

.

Coordinator
Jul 14, 2011 at 9:32 AM

What difficulties are you facing specifically? You should be able to create this with a part, a driver, a migration, and a controller action to handle the ajax requests.

Jul 14, 2011 at 12:48 PM

I'm receiving this error message when I add the model refernece to the view cshtml file.

 

Server Error in '/cs-orchard' Application.

The model item passed into the dictionary is of type 'IShapeProxy5ae85a47f6d14e3684b4897e9f9f93cd', but this dictionary requires a model item of type 'Calc.Models.ContactUsFormViewModel'.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The model item passed into the dictionary is of type 'IShapeProxy5ae85a47f6d14e3684b4897e9f9f93cd', but this dictionary requires a model item of type 'Calc.Models.ContactUsFormViewModel'.

Source Error:

Line 131:
Line 132:            var htmlHelper = new HtmlHelper(displayContext.ViewContext, displayContext.ViewDataContainer);
Line 133:            var result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);
Line 134:
Line 135:            Logger.Information("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);

 

The code I'm using that generates this is:

Controllers/HomeController.cs

using System;
using System.Web.Mvc;
using Orchard;
using Calc.Models;
using Orchard.Themes;

namespace Calc.Controllers
{
    [Themed]
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(new ContactUsFormViewModel());
        }
        [HttpPost]
        public ActionResult Index(ContactUsFormViewModel form)
        {
            if (!ModelState.IsValid)
            {
                return View(form);
            }
            form.Total = Calculate(form);

            return View("Sent", form);
        }
        private int Calculate(ContactUsFormViewModel form)
        {
            int vNum1 = form.Num;
            int vNum2 = form.Num2;
            var Total = vNum1 + vNum2;
            return Total;
        }
    }
}


Drivers/UnpersistedPartDriver.cs

using Orchard.ContentManagement.Drivers;
using Orchard.Localization;
using Jumpstart.Models;

namespace Szmyd.Orchard.Modules.Jumpstart.Drivers
{
    public class UnpersistedPartDriver : ContentPartDriver<UnpersistedPart>
    {
        public Localizer T { get; set; }

        public UnpersistedPartDriver() {
            T = NullLocalizer.Instance;
        }
        protected override DriverResult Display(UnpersistedPart part, string displayType, dynamic shapeHelper)
        {
            return ContentShape("Parts_Jumpstart_Unpersisted",
                () => shapeHelper.Parts_Jumpstart_Unpersisted(ContentPart: part));
        }
    }
}


Models/CalcModel.cs

using System;
using System.Collections.Generic;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Common.ViewModels;
using Orchard.Core.Common.Models;
using System.ComponentModel.DataAnnotations;

namespace Calc.Models
{
    public class ContactUsFormViewModel
    {
        [Required(ErrorMessage = "Please enter a number")]
        public int Num1 { get; set; }

        [Required(ErrorMessage = "Please enter a number")]
        public int Num2 { get; set; }
        public int Total { get; set; }
    }
}


Models/UnpersistedPart.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard.ContentManagement;

using System.ComponentModel.DataAnnotations;

namespace Jumpstart.Models
{
    public class UnpersistedPart : ContentPart
    {
        public class ContactUsFormViewModel
        {
            [Required(ErrorMessage = "Please enter a number")]
            public int Num1 { get; set; }

            [Required(ErrorMessage = "Please enter a number")]
            public int Num2 { get; set; }

            public int Total { get; set; }
        }
    }
}


Views/Parts/Jumpstart.Unpersisted.cshtml

@model Calc.Models.ContactUsFormViewModel
@{
    Layout.Title = "Calculator";
}
<h1>@Layout.Title</h1>
@using (Html.BeginFormAntiForgeryPost()) {
    <fieldset>
    <br />
        <p>Number 1:
            @Html.TextBoxFor(m => m.Num1)
            @Html.ValidationMessageFor(m => m.Num1)</p>

        <p>Number 2:
            @Html.TextBoxFor(m => m.Num2)
            @Html.ValidationMessageFor(m => m.Num2)</p>

        <p><input class="button primaryAction" type="submit" value="@T("Send")" /></p>


Thanks in advance bert and leroy

Jul 14, 2011 at 3:42 PM

In your view, change 

@model Calc.Models.ContactUsFormViewModel

to

@model dynamic

The problem is that the view model is proxied before being passed in to the view. Using @model dynamic only checks things like method calls when they are invoked, allowing you to use the proxied object as if it were an instance of ContactUsFormViewModel (which it is, really, but with some extra bits).


 

Jul 14, 2011 at 3:53 PM

Thanks Kobowi, I tried your solution but I get this problem now?

Server Error in '/cs-orchard' Application.

Compilation Error

Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.

Compiler Error Message: CS1963: An expression tree may not contain a dynamic operation

Source Error:


Line 8:      <br />
Line 9:  		<p>Number 1:
Line 10: 			@Html.TextBoxFor(m => m.Num1)
Line 11: 			@Html.ValidationMessageFor(m => m.Num1)</p>
Line 12: 



Jul 14, 2011 at 4:35 PM

Ok, I've just looked at some code and noticed that in one of my widgets, it doesn't declare @model at all, so try removing the line.

Apologies for the bad advice previously - its been a week or two since I've written any widgets . The concept remains the same though - the object that is passed to the view is a dynamically created subclass of your viewmodel, and whatever is checking the @model declaration the top of the page expects exactly that type.

Jul 15, 2011 at 3:25 PM

Hi Kobowi, I tried that and the same error persists.

An expression tree may not contain a dynamic operation

Jul 15, 2011 at 4:14 PM

I've just cottoned on - you are using Html.TextBoxFor rather than Html.TextBox? The latter will still work (it just doesn't do the clever stuff with expressions to work out the name, AFAIK - so you will have to explicitly tell it the name), and just casting the value will get rid of the warning about dynamic types:

@Html.TextBox("Num1", (string)Model.Num1)

Heres a snippet from something I was working on recently - which submits a form from a front-end content part view:

@Html.Hidden("bId", (int)Model.Part.Id)

Clearly a bit more verbose but it should allow you to get around this.