Knockout.js mapping a JSON into an observable-array - mapping

I want to build a client for my REST-Service using Knockout.js.
I have a lot of Repositorys i want to access through different urls - so i came up with this solution using the Revealing-Prototype-Pattern.
My problem: I can not find out how to map the ItemsProperty with my "data" i receive from my service.
var Repository = function (url) {
this.Url = url;
this.Items = ko.observableArray([]);
this.PendingItems = ko.observableArray([]);
};
Repository.prototype = function () {
var
getAllItems = function () {
var self = this;
$.getJSON(self.Url, function (data) {
// data=[{"Id":1,"Name":"Thomas","LastName":"Deutsch"},{"Id":2,"Name":"Julia","LastName":"Baumeistör"}]
ko.mapping.fromJS(data, self.Items);
});
},
...
// i call it like this:
customerRepository = new Repository('http://localhost:9200/Customer');
customerRepository.getAllItems();
I think the problem is in this: ko.mapping.fromJS(data, self.Items); but i can not find the right way to do it.
Question: what am i doing wrong? i have found an example - and they are doing the same i think: http://jsfiddle.net/jearles/CGh9b/

I believe that the two argument version of fromJS is only used for objects that were previously mapped, i.e they had an implicit empty mapping options object. Since your mapping is the first time it has been run, it needs to provider that empty options object like so.
ko.mapping.fromJS(data, {}, self.Items);
http://jsfiddle.net/madcapnmckay/j7Qxh/1/
Hope this helps.

Related

Bind Element to controls SAPUI5

I'm trying to show some data retrieved by Odata Model on a XML View.
In Component.js, I create a model with service Url and it works as usual.
var oDataModel = new sap.ui.model.odata.v2.ODataModel("http://server:port/sap/opu/odata/SAP/ZWYMB_SRV", {
user:"abapleader",
password: "TH123456789a#",
headers: {
"sap-client": 300
},
useBatch:false
});
this.setModel(oDataModel, "oDataModel");
So far, I've managed to get data to master using model.read() function.
Now I need to show the detail view. My code for onInit event is below:
this.router.getRoute("zwm01detail").attachPatternMatched(this._onObjectMatched.bind(this));
},
_onObjectMatched: function(oEvent) {
var that = this;
var MaWorkDoc = oEvent.getParameter("arguments").MaWorkDoc;
this.getModel("oDataModel").metadataLoaded().then(function() {
var sPath = that.getModel("oDataModel").createKey("/WorkDocList", {
MaWorkDoc: MaWorkDoc,
Pernr: "0001"
});
console.log(sPath);
that.getView().bindElement({
path:sPath
});
});
The sPath as I printed out using console.log(sPath) : /WorkDocList(MaWorkDoc='1110000001',Pernr='0001'), which I think, is correct. I also implemented and tested the back-end using this path and basically things are fine. But I don't know why I cannot show data on the view.
Detail.view.xml:
<Text text="{/MaWorkDoc}" maxLines="0"/>
Any suggestions, please?
Because you have given your model a name you will need to use that name in the binding. i.e. <Text text="{oDataModel>/MaWorkDoc}" maxLines="0"/>
So I've been working around with the docs and figure out there is model parameter which helps.
To be more specific, I add declare model in view.bindElement as below:
that.getView().bindElement({
path:sPath,
//** oDataModel = name of the model
model: "oDataModel"
});
If there is any better solution, I'd very glad to know it. Thank you.!
I do not think the detail binding code should be inside the .metadataLoaded handler. Rather it should be directly inside _onObjectMatched.
I mean like this.
_onObjectMatched: function(oEvent) {
var that = this;
var MaWorkDoc = oEvent.getParameter("arguments").MaWorkDoc;
var sPath = that.getModel("oDataModel").createKey("/WorkDocList", {
MaWorkDoc: MaWorkDoc,
Pernr: "0001"
});
console.log(sPath);
that.getView().bindElement({
path:sPath
});
}

BreezeJS executeQueryLocally and return string

I can execute a query in breeze from the server (using EF) which returns a load of boostrap data thus:
em.executeQuery(_lookupsQuery).then(function (data) {
_lookups = data.results;
console.log(_lookups[0].currentUserId);
This returns currentUserId which is a guid. I then store em using local storage for querying locally later:
_lookups = [{
currentUserId: em.executeQueryLocally(_lookupsQuery.toType(breeze.DataType.String))
}];
However this does not work as it requires an entity type e.g:
em.executeQueryLocally(_lookupsQuery.toType(em.metadataStore.getEntityType("Measure")))
Since currentUserId is a guid I am not sure which type to cast the query to. I have tried to make an entity type on the client just for this but it does not seem to work. Any help on solving this would be appreciated.
Edit:
After a suggestion, I modified lookups:
[HttpGet]
public async Task<object> Lookups()
{
var currentUser = await UserManager.FindById(Guid.Parse(User.Identity.GetUserId()));
var companyId = currentUser.CompanyId.Value;
return new
{
currentUser = new
{
Id = currentUser.Id
}
};
}
When querying remotely using:
em.executeQuery(_lookupsQuery).then(function (data) {
_lookups = data.results;
console.log(_lookups[0].currentUser);
I get:
Object { id="f2dceb4b-29e7-4533-99e2-2052dc39143a"}
I set up the new entity type:
metadataStore.addEntityType({
shortName: "CurrentUser",
dataProperties: {
id: { dataType: "String", isPartOfKey: true }
}
});
but when I query locally:
_lookups = [{
currentUser: em.executeQueryLocally(_lookupsQuery.toType(em.metadataStore.getEntityType("CurrentUser"))) }];
console.log(_lookups[0].currentUser);
this returns []
What am I doing wrong?
There are a few ways you can handle it. I am only going to touch on the two most basic methods that I personally use and hopefully one sparks your interest.
-1. Create an entityType in your metadataStore for user-type information.
metadataStore.addEntityType({
shortName: "User",
dataProperties: {
userId: { dataType: "String", isPartOfKey: true },
userName: { dataType: "String" }
}
});
This will add an entity type that you can serialize your return results to. It is important to note here that if you change anything about that user without accepting changes locally it will try to save it next time you call saveChanges() so make sure you handle those situations if applicable.
Of course that isn't the only option. You can most certainly just grab that user id from the query results without Breeze ever knowing about it or what it is for.
-2. POJO
function user(data) {
var self = this;
self.UserId = data.userId;
self.UserName = data.userName;
}
// After your query returns data
query.execute().then(userReturned);
function userReturned(data) {
// data is the returned object, which contains
// an httpResponse which is what breeze returns
// as the raw response
new user(data.httpResponse.data);
}
This method basically just grabs the httpResponse when the promise returns and uses it to create a Plain Old JavaScript Object without Breeze knowing about it. Of course for this to work you need to examine your data object that is returned and find what you are looking for and serialize that.
Edit
Your query locally should look like this -
query = breeze.entityQuery().from("Whatever").toType("CurrentUser").executeLocally();
em.executeQueryLocally(query);

how to create dynamic url in collection and model using backbone

My collection and model like this:
detail_userid = 0;
detail_contactid = 0;
var ContactDetail = Backbone.Model.extend({
urlRoot: URL_CONTACTS1+detail_userid+"/"+detail_contactid
});
var ContactDetailCollection = Backbone.Collection.extend({
model: ContactDetail,
url: URL_CONTACTS1+detail_userid+"/"+detail_contactid
})
The entrance is:
ContactDetailManagePageModel.prototype.init = function(m,n){
detail_userid = m;
detail_contactid = n;
var myContactDetails = new ContactDetailCollection();
var contactDetailListView = new ContactDetailListView({
collection: myContactDetails
});
myContactDetails.fetch({reset:true});
}
But when it runs,the url is :http://localhost:8080/ws/users/contacts/0/0,it means that the assignment to detail_userid and detail_contactid is unsuccessful,I don't know why.
Hope for your help.Thanks.
I think you are statically definining the urlRoot and url properties before you are running the init of the PageModel (not quite sure where you are getting m and n from though...)
Both url and urlRoot can be a function, so you can pass in options during instantiation and have them dynamically set on the model.
Simple example covering defining the collection and then creating one
var ContactDetailCollection = Backbone.Collection.extend({
model: ContactDetail,
url: function(){
return URL_CONTACTS1 + this.options.detail_userid + "/" + this.options.detail_contactid;
}
});
var myContactDetails = new ContactDetailCollection({
detail_userid: foo,
detail_contactid: bar
});
As I mentioned, I'm not sure what your init function is doing, I'm guessing it's something custom from your app that I don't need to worry about.
I'm fairly sure the main thing to take away is to set url and urlRoot dynamically
I would fulfill the accepted answer with few remarks.
First parameter when initializing Backbone.Collection is array of models, then options. To create an empty collection with options you should do next
var c = new Backbone.Collection(null, {opt1: val1, opt2: val2});
Actually, you can't access this.options in url function, bec. there are no options like in a model. What you can do, is assign required properties from options upon initialization.
initialize: function (models, options) {
// `parseInt()` is used for consistency that `id` is numeric, just to be sure
this.detail_userid = parseInt(options.detail_userid);
this.detail_contactid = parseInt(options.detail_contactid);
}
Later you can access them like this:
url: function() {
return URL_CONTACTS1 + this.detail_userid + "/" + this.detail_contactid;
}
I wanted to use the HATEOAS href from one model to fetch data of another model. It worked to simply set the url on the newly created collection instead of defining it right away in the constructor.
var DailyMeasuresCollection = Backbone.Collection.extend({
//url : set dynamically with collection.url = url
model : DailyMeasuresModel,
parse : function(data) {
return data._embedded.dailyMeasures;
}
});
var DailyMeasuresTopicListItemView = Backbone.View.extend({
//...
events : {
'click .select-topic' : 'onClick'
},
onClick : function() {
var topicMeasures = new DailyMeasuresCollection()
topicMeasures.url = this.model.attributes._links.measures.href // <- here assign
var topicMeasuresView = new DailyMeasuresListView({
collection : topicMeasures
});
topicMeasures.fetch()
}
});

Breeze querying local cache with EF and Web API

Problem
I have a view with 6 drop downs. Each of which is being populated by a Web API call. I want
to use breeze to run the query locally once it has populated from the remote server
The code runs fine when the data call is against the server. The issue is when trying to query the local cache. I never get any results returned. Is my approach flawed or am I doing something wrong ?
SERVER SIDE
View model
class genericDropDown()
{
public int value{get;set;}
public string option{get;set;}
}
The WebAPI [A single sample method]
[HttpGet]
// GET api/<controller>
public object GetSomeVals()
{
return _context.getClinician();
}
The Repository [A single sample method]
public IEnumerable<genericDropDown> getDropDownVal()
{
return context.somemodel(a=>new{a.id,a.firstname,a.lastname}).ToList().
Select(x => new GenericDropDown
{ value = x.id, option = x.firstname+ " " + x.lastname});}
}
CLIENT SIDE
Datacontext.js
var _manager = new breeze.EntityManager("EndPoint");
//Being called from my view model
var getDropDownBindings = function(KO1, KO2) {
//First add the entity to the local metadatastore then populate the entity
$.when(
addDD('clinicianDropDown', webAPIMethod),
getData(KO1, webAPIMethod, null, 'clinicianDropDown'),
addDD('docTypeDropDown', webAPIMethod);
getData(KO2, webAPIMethod, null, 'docTypeDropDown'),
).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
logger.log('Got drop down vals', "", 'dataContext', true);
}
};
//Add the entity to local store. First param is typename and second is
resource name (Web API method)
var addDD = function(shortName,resName) {
_manager.metadataStore.addEntityType({
shortName: shortName,
namespace: "Namespace",
autoGeneratedKeyType: breeze.AutoGeneratedKeyType.Identity,
defaultResourceName:resName,
dataProperties: {
value: { dataType: DataType.Int32,
isNullable: false, isPartOfKey: true },
option: { dataType: DataType.String, isNullable: false }
}
});
return _manager.metadataStore.registerEntityTypeCtor(shortName, null, null);
};
//Get the data
var getData = function(observableArray, dataEndPoint, parameters, mapto) {
if (observableArray != null)
observableArray([]);
//TO DO: Incorporate logic for server or local call depending on
// whether this method is accessed for the first time
var query = breeze.EntityQuery.from(dataEndPoint);
if (mapto != null && mapto != "")
query = query.toType(mapto);
if (parameters != null)
query = query.withParameters(parameters);
//This approach doesnt work on local querying as Jquery complains
//there is no 'then' method. Not sure how to implement promises
//when querying locally
/* return _manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
if (observableArray != null)
observableArray(data.results);
}
*/
//The array length from this query is always 0
var data = _manager.executeQueryLocally(query);
observableArray(data.results);
return;
};
//Generic error handler
function queryFailed(error) {
logger.log(error.message, null, 'dataContext', true);
}
viewmodel.js
//In Durandal's activate method populate the observable arrays
dataContext.getDropDownBindings (KO1,KO2);
Viewmodel.html
<select class="dropdown" data-bind="options: KO1, optionsText: 'option', value: 'value', optionsCaption: 'Clinicians'"></select>
<select class="dropdown" data-bind="options: KO2 optionsText: 'option', value: 'value', optionsCaption: 'Document Types'"></select>
You can only execute local queries against types that are described by metadata.
Without more information I can't be sure, but my guess is that your GetSomeVals method is not returning 'entities' but just loose data. In other words, the types of objects returned from the GetSomeVals method must be entities (or contain entities within a projection) in order for breeze to be able to perform a local query. This is because Breeze knows how to cache and query entities but has no ideas how to cache 'arbitrary' query results.
Note that you can return an anonymous type containing entities of different types from the server, (in order to populate mostly static small datasets), but the individual items must be 'entities'. In this case, Breeze will take apart the anon result and pick out any entities to include in the EntityManager cache.
Per you question of how to perform an local query with promises, use the FetchStrategy.FromLocalCache with the using method.
i.e. this query
var results = em.executeQueryLocally(query)
can also be expressed as:
query = query.using(FetchStrategy.FromLocalCache);
return em.executeQuery(query).then(data) {
var results = data.results;
}
The local query is still executed synchonously but is made to look async.

Returning an List<> with JsonResult

I am using jQuery to consume my action.
$.ajax({ url: '',
data: 'json',
type: 'post',
dataType: options.dataType,
async: false,
success: function (obj) { returnValue = obj; }
});
now....
if I return this one...
var test1 = Json(new { Message = "Any message here.", Status="True" });
the return on my Ajax call is JUST fine..
BUT.. if i return this one(List of users from EF)
var test2 = Json(IoC.Resolve<IUserService>().GetUsers());
I will get undefined/500 (Internal Server Error).
Now the question is how to properly return a JSON from an object so that jQuery ajax can read it properly?
Thanks
Lee,
Have never seen the usage:
var test2 = Json(IoC.Resolve<IUserService>().GetUsers());
before and am not going to comment. However, you MAY get away with merely adding a .ToList() to the end of the statament, i.e.:
var test2 = Json(IoC.Resolve<IUserService>().GetUsers().ToList());
The reason for the issue is due to the fact that you haven't yet enumerated the object out before attempting to populate the json object, therefore you experience all sorts of latency and object reference problems. By adding ToList() you mitigate these problems as the object is fully enumerated before it hits the Json() method.
it might just work, ... or blow right up :)
public ActionResult MethodName(){
var returnList = IoC.Resolve<IUserService>().GetUsers().ToList();
retun JSON(returnList,JSONRequestBehaviour.AllowGet);
}
jQuery Function
success: function (obj) {
alert(obj.d.ListProperty);
// you can access all the properties you have in the list.
}

Resources