Unknown's avatar

Knockout options binding not refreshing – Breeze

I’m coding a detail screen with a drop down showing instances of child entities. The user can add a new instance of a child entity and the fields for the new instance appear for editing below. Alternatively the user can select an existing instance from the drop down and edit that.
When the user adds a new child entity, they need the dropdown focussed on the new child entity name and the fields for the new entity displayed for editing below.
The fields were appearing, but the drop down wasn’t refreshing.
Html for drop down:

    <div class="col-md-8">
      <div class="form-group">
        <label for="childItemList">Child items:</label>
        <select class="form-control" id="childItemList"
                data-bind="options: childItems, optionsText: 'name',value: $root.selectedChildItem, height:3"></select>
      </div>

    </div>

The binding context is a “with:parentEntity” binding. The solution was pretty simple, call “valueHasMutated” on the parent after the new child entity is created:

        addChildEntity = function () {
            var newChildEntity = datacontext.createChildEntity(parentEntity());
            parentEntity.valueHasMutated();
            selectedChildEntity(newChildEntity);
        },
Unknown's avatar

Field validation with Knockout and Breeze

I’ve struggled a little with this today. I’m used to knockout validation, and I wanted to make a start using Breeze validation to compare two date fields bound using knockout.

This is what I wanted to achieve:

date validation screenshot

Here’s a reminder on how to make this work:

I’m basing my app on the nuget package “ProvenStyle.Durandal.StarterKit” v 0.0.7. This is pretty much brand new at the time of writing and I’ve found it to be a great starting point, together with these Breeze packages:

  <package id="bootstrap" version="3.0.3" targetFramework="net451" />
  <package id="Breeze.Client" version="1.4.11" targetFramework="net451" />
  <package id="Breeze.Server.ContextProvider" version="1.4.11" targetFramework="net451" />
  <package id="Breeze.Server.ContextProvider.EF6" version="1.4.11" targetFramework="net451" />
  <package id="Breeze.Server.WebApi2" version="1.4.11" targetFramework="net451" />
  <package id="Breeze.WebApi2.EF6" version="1.4.11" targetFramework="net451" />

As per the great John Papa’s advice in his 2013 course “Single Page Apps Jumpstart” on Pluralsight, I created a datacontext to handle things like the configuration of the Breeze EntityManager. In this code snippet from the datacontext module, I get the metadata and then call a function “initializeCustomValidators” which I’ve called from a module called “model”, ( again recommended by John P).

 

        var primeData = function () {
            return manager.fetchMetadata()
                .then(function (data) {
                    utils.getOptionSets(data.schema.enumType, optionSets);
                    model.initializeCustomValidators(manager);
                    Q.resolve();

                }).fail(function (error) {
                    Q.reject(error);
                });

        };

The initializeCustomValidators function is just configuring one validator here for “end Date” (“end”). This works at the entity level and supplies a validator object with details of the “end” property:

 

    function initializeCustomValidators(manager) {

        // returns an endDateValidator with its parameters filled
        var endDateValidator = new breeze.Validator(
                "endDateValidator",  // validator name
                endDateValidationFn, // validation function
                {                    // validator context
                    messageTemplate: "'start and end date' must be present, start must be less or equal to end",
                    property: manager.metadataStore.getEntityType("ClientLicense").getProperty("end"),
                    propertyName: "end"
                });
        
        function endDateValidationFn(entity, context) {
            var end = entity.getProperty("end");
            var start = entity.getProperty("start");
            if (end === undefined || start === undefined) {
                return false;
            }
            return start <= end;

        };
        
        var clientLicenseType = manager.metadataStore.getEntityType("ClientLicense");
        clientLicenseType.validators.push(endDateValidator);


    };

It’s worth stepping through the “getValidationErrors in breeze.debug.js to see how this works, this helped me populate the validator correctly ( specifically looking at how the filter works, see below, this helped to supply the correct parameter for property, above).

    proto.getValidationErrors = function (property) {
        assertParam(property, "property").isOptional().isEntityProperty().or().isString().check();
        var result = __getOwnPropertyValues(this._validationErrors);
        if (property) {
            var propertyName = typeof (property) === 'string' ? property : property.name;
            result = result.filter(function (ve) {
                return ve.property && (ve.property.name === propertyName || (propertyName.indexOf(".") != -1 && ve.propertyName == propertyName));
            });
        }
        return result;
    };

This  suggests one way to listen for changes to the Breeze validation errors connection and update a ko observable to which I can bind. Very handy. I call this in the EntityType post-construction initializer as suggested by Ward:

    function addhasValidationErrorsProperty(entity) {

        var prop = ko.observable(false);

        var onChange = function () {
            var hasError = entity.entityAspect.getValidationErrors().length > 0;
            if (prop() === hasError) {
                // collection changed even though entity net error state is unchanged
                prop.valueHasMutated(); // force notification
            } else {
                prop(hasError); // change the value and notify
            }
        };

        onChange();             // check now ...
        entity.entityAspect // ... and when errors collection changes
            .validationErrorsChanged.subscribe(onChange);

        // observable property is wired up; now add it to the entity
        entity.hasValidationErrors = prop;
    }

I call it like this from my model class:

    function clientLicenseInitializer(clientLicense) {
        addhasValidationErrorsProperty(clientLicense);

        clientLicense.name = ko.computed(function() {
            var orgName = clientLicense.crmOrganisationName() ?clientLicense.crmOrganisationName():'--';
            var type = clientLicense.licenseType() ? clientLicense.licenseType():'--';
            var hostingModel = clientLicense.hostingModel() ? clientLicense.hostingModel():'--';
            return orgName + ', ' + type + ', ' + hostingModel;
        });

    }

data binding is done as suggested in Ward’s post:

      <div class="row">
        <div class="col-md-6">
          <div class="form-group">
            <label class="control-label">Start Date</label>

            <input data-bind='datepicker:start' class="form-control">
          </div>
        </div>
        <div class="col-md-6">
          <div class="form-group">
            <label class="control-label">End Date</label>

            <input data-bind="datepicker:end" class="form-control">
            <div data-bind="if: hasValidationErrors">
              <pre data-bind="text: entityAspect.getValidationErrors('end')[0].errorMessage "></pre>
            </div>
          </div>
        </div>
      </div>
Unknown's avatar

Breeze 1.4.11 breaks MVC Spa with Durandal 2.0.1 starter kit

I created a Web API/ MVC project in VS 2013 using the SPA template, added the Durandal Starter Kit NuGet package, which pulls in Bootstrap 3, which worked fine. When I added the Breeze Client and Server Package and ran, the app fell over:

‘Could not load file or assemblyMicrosoft.Data.Services.Client, Version=5.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′

The version installed in the project was 5.6.1.0

Added the following to web config & this fixed the issue:

      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Data.OData" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.6.0.0" newVersion="5.6.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.6.0.0" newVersion="5.6.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Spatial" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.6.0.0" newVersion="5.6.1.0" />
      </dependentAssembly>
Unknown's avatar

Breeze metadata service returns 404

I’m experimenting with Breeze as I’m rewriting a legacy Web Forms app as a SPA using MVC and Web API.

I’m using EF6 and it’s clear Breeze is going to cut out an awful lot of work writing JavaScript model classes, mapping code and data contexts on the client, not to mention filters and projections. I’m going to post a quick guide to setting up Breeze with EF6 ( mainly for myself ) later. I noticed I’m getting 404s on the metadata service Breeze calls if I stop the app in debug mode and then re run.

This Stackoverflow answer appears to have solved this issue.

http://stackoverflow.com/questions/15715701/metadata-query-failed-for-breeze-js

.. Relative URLS seem work just as well. See the url passed to the breeze.DataService object below.

define('app/vm.accounts', [
'ko', 'breeze', 'app/config'
], function (ko, breeze, config)  {
          var
          accounts  = ko.observableArray([])
          , dataService = new breeze.DataService({
                    serviceName: "/api/licensing/"
                    , hasServerMetadata: false
          })
          , em = new breeze.EntityManager({
                    dataService:dataService
          })
          , currentAccount = ko.observable()
          , logger = config.logger
          , getAccounts = function()  {
                    var query  = breeze.EntityQuery.from("Accounts")
                    em.executeQuery(query)
                    .then(function(data)  {
                              accounts(data.results);
                    })
                    .fail(errorLoading);
          }
          , errorLoading = function(error)  {
                    logger.error(error.message, "Loading failed");
          };
          return  {
                    accounts: accounts, 
          };
});