Pregunta

I'm new at Knockout JS and i am struggling with this issue, need your guidance. Everything works fine i am fable to get ProductID and it's ProductOffers VIA Ajax but when i second dropdown doesn't populates itself.

<table>
    <thead>
        <tr>
            <th></th>
            <th>Product</th>
            <th>Product Offers</th>
            <th>Price</th>
            <th>Stock</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <select data-bind="options: Products, optionsText: 'Name',optionsValue: 'ID', value: ProductID, optionsCaption: '-'" />
            </td>
            <td data-bind="if: ProductID">
                <select data-bind="options: ProductOffers, optionsText: 'Name',optionsValue: 'ID', value: ProductOfferID, optionsCaption: '-'" />
            </td>
            <td></td>
            <td></td>
        </tr>
    </tbody>
</table>

<script type="text/javascript">

    function Product(id, name) {
        this.ID = id;
        this.Name = name;
    }
    function Offer(id, name) {
        this.ID = id;
        this.Name = name;
    }

    var viewModel = {
        Products: ko.observableArray(<%= LoadProducts() %>),

        ProductID: ko.observable('0'),
        ProductOfferID: ko.observable('0'),

        ProductOffers: ko.observable("")
    };

        viewModel.ProductID.subscribe(function (newValue) {
            if (typeof newValue != "undefined") {
                //alert("Selected product is : " + newValue);
                viewModel.ProductOffers = GetProductOffers(newValue);
                //alert(viewModel.ProductOffers);
            }
        });

        ko.extenders.numeric = function (target, precision) {
            //create a writeable computed observable to intercept writes to our observable
            var result = ko.computed({
                read: target,  //always return the original observables value
                write: function (newValue) {
                    var current = target(),
                roundingMultiplier = Math.pow(10, precision),
                newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue),
                valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;

                    //only write if it changed
                    if (valueToWrite !== current) {
                        target(valueToWrite);
                    } else {
                        //if the rounded value is the same, but a different value was written, force a notification for the current field
                        if (newValue !== current) {
                            target.notifySubscribers(valueToWrite);
                        }
                    }
                }
            });

            //initialize with current value to make sure it is rounded appropriately
            result(target());

            //return the new computed observable
            return result;
        };

        ko.applyBindings(viewModel);

        function GetProductOffers(ProductID) {

            alert("Fetching offers for Product : " + ProductID)

            var Val = "";
            jQuery.ajax({
                type: "POST",
                url: "testing.aspx/GetProductOffers",
                data: "{ProductID: '" + ProductID + "'}",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                async: false,
                success: function (msg) {
                    Val = msg.d;
                },
                error: function (jqXHR, exception) {
                    if (jqXHR.status === 0) {
                        alert('Not connect.\n Verify Network.' + jqXHR.responseText);
                    } else if (jqXHR.status == 404) {
                        alert('Requested page not found. [404]' + jqXHR.responseText);
                    } else if (jqXHR.status == 500) {
                        alert('Internal Server Error [500].' + jqXHR.responseText);
                    } else if (exception === 'parsererror') {
                        alert('Requested JSON parse failed.' + jqXHR.responseText);
                    } else if (exception === 'timeout') {
                        alert('Time out error.' + jqXHR.responseText);
                    } else if (exception === 'abort') {
                        alert('Ajax request aborted.' + jqXHR.responseText);
                    } else {
                        alert('Uncaught Error.\n' + jqXHR.responseText);
                    }
                }
            });
            return Val;
        }
</script>

**EDIT : ** here is a what is hapening after tim's input. my problem

JSFiddle : http://jsfiddle.net/neodescorpio/sPrVq/1/

EDIT :

here is the web method , i have changes it to produce a valid JSON acording to JSLint. Now the 2nd dropdown fills up but the problem is it's values never change whenever i change products, correct values are fetched but dropdown doesn't shows them.

    [WebMethod]
public static string GetProductOffers(long ProductID)
{
    StringBuilder sbScript = new StringBuilder();
    string json = "[{\"ID\": 0,\"Name\": \"Sorry ! No data found\"}]";
    bool first = true;

    List<DMS.ProductOfferDO> offers = ProductOffers.Get(ProductID);

    if (offers != null && offers.Count > 0)
    {
        sbScript.Append("[");
        foreach (var x in offers.OrderBy(d => d.IsCashOffer))
        {
            if (first)
            {
                sbScript.Append(string.Format("{{\"ID\": {0},\"Name\": \"{1}\"}}", x.ID, x.Name));
                first = false;
            }
            else
            {
                sbScript.Append(string.Format(",{{\"ID\": {0},\"Name\": \"{1}\"}}", x.ID, x.Name));
            }
        }
        sbScript.Append("]");
        json = sbScript.ToString();
    }
    return json;
}
¿Fue útil?

Solución

Why are you declaring ProductOffers as ko.observable("")? It should be declared as an observable array instead: ProductOffers: ko.observableArray([]);

Also, in your JFiddle:

function GetProductOffers(ProductID) {
    var Val = "[new Offer(1,'Name'),new Offer(2,'Product A'),new Offer(4,'Product B'),new Offer(5,'Product C')]";               
    return Val;
}

Should be:

function GetProductOffers(ProductID) {
    var Val = [new Offer(1,'Name'),new Offer(2,'Product A'),new Offer(4,'Product B'),new Offer(5,'Product C')];             
    return Val;
}

http://jsfiddle.net/sPrVq/2/

EDIT:

Try to modify your setup as follows:

  [WebMethod]
public static string GetProductOffers(long ProductID)
{   
    List<DMS.ProductOfferDO> offers = ProductOffers.Get(ProductID);

    return JsonConvert.SerializeObject(offers);
}

You need to import: using Newtonsoft.Json; first of course.

And why did you use post? It should be a get:

function GetProductOffers(ProductID) {

         $.get("testing.aspx/GetProductOffers",
            { ProductID: ko.toJSON(ProductID) }
            )
            .done(function (data) {
                 viewModel.ProductOffers(JSON.parse(data));
            })
            .fail(function (data) { })
            .always(function () { });

}

EDIT2:

viewModel.ProductID.subscribe(function (newValue) {

    viewModel.ProductOffers.removeAll();

    if (newValue) {
        var productOffers = GetProductOffers(newValue);
        viewModel.ProductOffers(productOffers);
    }

});

Let us know how this goes please!

Otros consejos

<td data-bind="with: ProductID"> is incorrect - this would only be necessary if you had a ProductID nested object. You don't so it is not needed. Just bind the second list to ProductOffers not $data.ProductOffers.

If what you are trying to achieve is to not show the second list until there are offers then just change <td data-bind="with: ProductID"> to <td data-bind="if: ProductOffers"> and that should work as expected.

EDIT:
I cannot say for sure as I don't know exactly what is returned, but my guess is there is a problem with ProductOffers. The ajax call returns the result and that is what you set ProductOffers to, but is the structure of each ProductOffer the same as what you have set for your select list binding? You have Name and ID as the bound values, are those properties of the items you return from your ajax call (GetProductOffers)?

EDIT 2
Ok, I see from your chat with Diana that your web method returns something like "[new Offer(..)...]". My guess is you have that method coded to simply return a string or something, right? You need to actually create a List<Offer> and then serialize that with something like JavascriptSerializer or JSON.net. You need to return JSON from the web method. It would be helpful if you showed the web method code, then we could help you figure out what is going wrong.

EDIT 3
I would definitely recomment not creating the JSON " by hand" - use either the built-in JavascriptSerializer or, better yet, use JSON.net. While it is not a must, it is cleaner and simpler. PLus, why re-invent the wheel, so to speak? Check Diana's answer out - she has besically outlined precisely what you need to do to get this to work. Good luck! :)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top