Get one database record, display it, update it, and save it back to the database - breeze

SOLVED! It was a Knockout issue (wrong binding). But maybe someone likes to argue or comment about the code in general (dataservice, viewmodel, etc).
I tried to build a Breeze sample, where I get one database record (with fetchEntityByKey), display it for updating, then with a save button, write the changes back to the database. I could not figure out how to get it to work.
I was trying to have a dataservice ('class') and a viewmodel ('class'), binding the viewmodel with Knockout to the view.
I very much appreciated if someone could provide a sample or provide some hints.
Thankx, Harry
var dataservice = (function () {
var serviceName = "/api/amms/";
breeze.NamingConvention.camelCase.setAsDefault();
var entityManager = new breeze.EntityManager(serviceName);
var dataservice = {
serviceName: serviceName,
entityManager: entityManager,
init: init,
saveChanges: saveChanges,
getLocation: getLocation
};
return dataservice;
function init() {
return getMetadataStore();
}
function getMetadataStore() {
return entityManager.fetchMetadata()
.then(function (result) { return dataservice; })
.fail(function () { window.alert("fetchMetadata:fail"); })
.fin(function () { });
}
function saveChanges() {
return entityManager.saveChanges()
.then(function (result) { return result; })
.fail(function () { window.alert("fetchEntityByKey:fail"); })
.fin(function () { });
}
function getLocation() {
return entityManager.fetchEntityByKey("LgtLocation", 1001, false)
.then(function (result) { return result.entity; })
.fail(function () { window.alert("fetchEntityByKey:fail"); })
.fin(function () { });
}
})();
var viewmodel = (function () {
var viewmodel = {
location: null,
error: ko.observable(""),
init: init,
saveChanges: null
};
return viewmodel;
function init() {
return dataservice.init().then(function () {
viewmodel.saveChanges = dataservice.saveChanges;
return getLocation();
})
}
function getLocation() {
return dataservice.getLocation().then(function (result) {
return viewmodel.location = result;
})
}
})();
viewmodel.init().then(function () {
ko.applyBindings(viewmodel);
});

Glad you solved it. Can't help noticing that you added a great number of do-nothing callbacks. I can't think of a reason to do that. You also asked for metadata explicitly. But your call to fetchEntityByKey will do that implicitly for you because, as you called it, it will always go to the server.
Also, it is a good idea to re-throw the error in the fail callback within a dataservice so that a caller (e.g., the ViewModel) can add its own fail handler. Without re-throw, the caller's fail callback would not hear it (Q promise machinery acts as if the first fail handler "solved" the problem).
Therefore, your dataservice could be reduced to:
var dataservice = (function () {
breeze.NamingConvention.camelCase.setAsDefault();
var serviceName = "/api/amms/";
var entityManager = new breeze.EntityManager(serviceName);
var dataservice = {
serviceName: serviceName, // why are you exporting this?
entityManager: entityManager,
saveChanges: saveChanges,
getLocation: getLocation
};
return dataservice;
function saveChanges() {
return entityManager.saveChanges()
.fail(function () {
window.alert("saveChanges failed: " + error.message);
throw error; // re-throw so caller can hear it
})
}
function getLocation() {
return entityManager.fetchEntityByKey("LgtLocation", 1001, false)
.then(function (result) { return result.entity; })
.fail(function () {
window.alert("fetchEntityByKey failed: " + error.message);
throw error; // re-throw so caller can hear it
})
}
})();
I don't want to make too much of this. Maybe you're giving us the stripped down version of something more substantial. But, in case you (or a reader) think those methods are always necessary, I wanted to make clear that they are not.

Related

OData V2 SetProperty in onInit

I need to set the property to DataSet during onInit, to change the visiblity of some controls in my View. I could solve this problem with setting the visibility dynamically in controller. But I want to use the expression binding and the visible property in the View to set visibilites.
I'm getting an error in the function OnStartSetVisibilites. self.getView().getBindingContext() returns UNDEFINED. Without the sPath, I can't use setProperty. And without setProperty, my View-Controls don't know the visible-value.
How to fix this?
View:
<uxap:ObjectPageSubSection visible="{= ${Responsible} === '1'}" id="leader">
</uxap:ObjectPageSubSection>
OnInit in View-Controller:
onInit: function () {
var startupParameters = this.getOwnerComponent().getComponentData().startupParameters;
var sWorkitem = startupParameters.TASK[0];
this.setModel(this.getOwnerComponent().getModel());
this.getModel().metadataLoaded().then(function () {
var sObjectPath = this.getModel().createKey("DataSet", {
Workitem: sWorkitem
});
this.getView().bindElement({
path: "/" + sObjectPath
});
}.bind(this));
var self = this;
var model = this.getOwnerComponent().getModel();
this.getModel().read("/CharSet", {
success: function (response) {
$.sap.Chars = response.results;
self.onStartSetVisibilities(model, self);
}
});
// self.getView().attachAfterRendering(function () {
// self.onStartSetVisibilities(model, self);
// });
}
OnStartSetVisibilities:
onStartSetVisibilities: function (model, self) {
var char = model.getProperty(„GeneralData/Char");
if (char !== "" || char !== null) {
model.setProperty(self.getView().getBindingContext().sPath + "/Responsible",
this.getResponsibleForChar(char));
}
}
I put together some code which might solve your issue (it's untested so it may contain syntax errors!).
I introduced the concept of Promises which simplifies the asynchronous behavior of JS. I also replaced some of the inner functions with Arrow functions so you don't have to deal with that or self. Arrow functions basically use the this of the scope they are defined within.
Your app should now have a proper flow.
First you bind the view.
After the view is bound you read the CharSet.
After the data is read you set the visibility stuff
onInit: function () {
const startupParameters = this.getOwnerComponent().getComponentData().startupParameters;
const sWorkitem = startupParameters.TASK[0];
this._bindView(sWorkitem)
.then(() => this._readCharSet())
.then(() => this._setVisibilities())
},
_bindView: function (sWorkitem) {
return new Promise((resolve) => {
const oModel = this.getOwnerComponent().getModel();
oModel.metadataLoaded().then(() => {
const sPath = "/" + oModel.createKey("DataSet", {
Workitem: sWorkitem
});
this.getView().bindElement({
path: sPath,
events: {
change: resolve,
dataReceived: resolve
}
});
});
});
},
_readCharSet: function () {
return new Promise((resolve) => {
const oModel = this.getOwnerComponent().getModel();
oModel.read("/CharSet", {
success: resolve
});
});
},
_setVisibilities: function () {
const oModel = this.getOwnerComponent().getModel();
const sChar = oModel.getProperty("GeneralData/Char");
if (sChar) {
// ...
}
}

Using a service worker to get list of files to cache from server

I´m trying to use a service worker in an existing asp mvc app. Generally, it´s working fine: I can cache files and so on. Problem is, that there are many files to be cached and I´m trying to return an array of paths to the service worker, so that the files can be added to cache without adding them manually.
Here´s what I have so far:
Controller:
public ActionResult GetFilesToCache()
{
string[] filePaths = Directory.GetFiles(Server.MapPath(#"~\Content"), "*", SearchOption.AllDirectories);
string[] cuttedFiles = new string[filePaths.Length];
int i = 0;
foreach (var path in filePaths)
{
cuttedFiles[i] = path.Substring(path.IndexOf("Content"));
i++;
}
return Json(new { filesToCache = cuttedFiles }, JsonRequestBehavior.AllowGet);
}
This gives me a string array with entries like "Content\image1.png" etc.
Service worker:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function (cache) {
console.log('[ServiceWorker] Caching app shell');
return fetch('Home/GetFilesToCache').then(function (response) {
return response;
}).then(function (files) {
return cache.addAll(files);
});
})
);
});
The error I get is:
Uncaught (in promise) TypeError: Failed to execute 'addAll' on 'Cache': Iterator getter is not callable.
Calling the action works just fine, data is received by the service worker, but not added to cache.
In the following code:
return fetch('Home/GetFilesToCache').then(function (response) {
return response;
}).then(function (files) {
return cache.addAll(files);
});
the value of the files parameter is going to be a Response object. You want it to be the JSON deserialization of the Response object's body. You can get this by changing return response with return response.json(), leading to:
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.addAll(files);
});
Got it working with this code:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function (cache) {
console.log('[ServiceWorker] Caching app shell');
return fetch('Home/GetFilesToCache').then(function (response) {
return response.json();
}).then(function (files) {
var array = files.filesToCache;
return cache.addAll(array);
});
})
);
});
Notice:
Chrome only lists a part of files stored in cache, so you just have to click that little arrow to show the next page:
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.addAll(files);
});
The returned file has "filesToCache" property on "files" and also use "add" instead of "addAll".
So you need to write the following way.
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.add(files.filesToCache);
});

Model Id passed through to Angular Ctrl

I have a MVV view that used the Controller below, I need to get the product Id, ie Model.Id through to the controller somehow.
I have tried the $scope.init but it is coming through as null when I am making the first Ajax call, I suspect that this ajax get is kicking off before the init is fired and setting the product id, so the ajax fails as the productId is null when the call is made. I am new to angular so if its a schoolboy error I apologise !.
Controller and HTML are shown below.
angular.module('askQuestions', [])
.controller('questionController', function ($scope, $http) {
$scope.loading = true;
$scope.addMode = false;
$scope.replyMode = false;
$scope.parentClicked = 0;
$scope.init = function (productId) {
//This function is sort of private constructor for controller
$scope.productId = productId;
$scope.getUrl = '/Api/GetProductQuestions/' + $scope.productId;
};
//Used to display the data
//$http.get('/Api/GetAllManufacturers?apiToken=6a5ce02e-0506-0a41-2f50-37327080662f').success(function (data) {
$http.get($scope.getUrl).success(function (data) {
$scope.questions = data;
$scope.loading = false;
})
.error(function () {
$scope.error = "An Error has occured while loading questions!";
$scope.loading = false;
// alert($scope.getUrl);
});
});
<div data-ng-app data-ng-controller="questionController" ng-init="init('#Model.Id')" class="container">
Your $http.get is evaluated in the instantiation of the controller. The instantiation is before your init, so the ajax call is already being made. You can easily fix this by wrapping your $http.get also in a function:
$scope.init = function (productId) {
//This function is sort of private constructor for controller
$scope.productId = productId;
$scope.getUrl = '/Api/GetProductQuestions/' + $scope.productId;
getData();
};
var getData = function() {
$http.get($scope.getUrl)
.success(function (data) {
// success
$scope.questions = data;
})
.error(function () {
// error
$scope.error = "An Error has occured while loading questions!";
})
.finally(function () {
// always
$scope.loading = false;
});
}

MVC: How to call Javascript function in action?

In my view I has a function called testalert,and in my controller I has a action called Index,I use javascriptmodel can solve my problem,but I find that if my action do not return a view(),for example:just return Json(model),the javascriptmodel will not work.How to call js function when I return json?Why do the javascriptmodel only be designed to work well
in return view?
function testalert(para) {
alert(para);
}
public ActionResult Index()
{
//work well and alert "abc"
this.AddJavaScriptFunction("testalert", PageLoadEvent.Ready, null, "abc");
return View();
}
public ActionResult GetData()
{
var restult="data";
// not work
this.AddJavaScriptFunction("testalert", PageLoadEvent.Ready, null, "abc");
return Json(restult);
}
I've done f.e AsyncHelper.Call(url, params) which return as a result promise, and on clientside wait for promise.done and to my stuff.
shorter version:
var AsyncAction = (function () {
return {
//options: passed to $.ajax
Call: function (options, helperOptions) {
return $.ajax(options)
.done(function (result) {
helperOptions.onSucceed(result.model);
});
}
};
})();

q.js automatically propagate errors (catch async thrown errors)?

i would like to know if there's any way to automatically propagate errors from a promise to another? IE: catch the thrown error from a nested promise.
for example, in the following code sample, the "internalWorker" nested promise function needs
.fail(function (error) {
return deferred.reject(error);
});
in order to propagate the error. if this line isn't contained, the error is throw to the top. (crashed app)
would it be possible to automatically propagate the error so i don't need to add .fail() functions to all my nested promises?
```
function top(input) {
var deferred = q.defer();
internalWorker(input).then(function (value) {
logger.inspectDebug("top success", value);
}).fail(function (error) {
return deferred.reject(error);
});
return deferred.promise;
}
function internalWorker(input) {
var deferred = q.defer();
q.delay(100).then(function () {
throw new Error("internal worker async error");
}).fail(function (error) {
return deferred.reject(error);
});
return deferred.promise;
}
top("hello").then(function (value) {
logger.inspectDebug("outside success", value);
}).fail(function (error) {
logger.inspectDebug("outside fail", error);
}).done();
```
If you are using https://github.com/kriskowal/q, this will do what you intend:
function top(input) {
return internalWorker(input).then(function (value) {
logger.inspectDebug("top success", value);
return value;
});
}
function internalWorker(input) {
return q.delay(100).then(function () {
throw new Error("internal worker async error");
return value;
});
}
top("hello").then(function (value) {
logger.inspectDebug("outside success", value);
}, function (error) {
logger.inspectDebug("outside fail", error);
}).done();
Return promises or values from within callbacks. Errors propagate implicitly.

Resources