I have a situation where my view definition(i.e. the content that gets displayed on a layout file) is defined in a json structure. I need to be able to define this json structure along with bindings and then my code should be able to dynamically create controls based on the json structure and fill out the layout.
I am able to accomplish the same using code below.
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
FirstViewModel firstViewModel = new FirstViewModel()
{
Id = 1001,
FirstName = "Amit",
MiddleName = string.Empty,
LastName = "Taparia",
Race = "Asian",
IsUSNational = false
};
this.ViewModel = firstViewModel as IMvxViewModel;
var bindings = this.CreateInlineBindingTarget<FirstViewModel>();
this.Root = new RootElement("Main View", null, null).
{
new Section("Personal Information")
{
new StringElement("Employee#","Enter Employee#").Bind(bindings,vm => vm.Id)
new EntryElement("FirstName","Enter First Name").Bind(bindings,vm=>vm.FirstName),
new EntryElement("MiddleName","Enter Middle Name").Bind(bindings, vm=> vm.MiddleName),
new EntryElement("LastName","Enter Last Name","Amit",null).Bind(bindings, vm => vm.LastName),
new EntryElement("Race","Enter Race").Bind(bindings,vm => vm.Race),
new BooleanElement("US National",true,).Bind(bindings, vm => vm.IsUSNational).
},
};
}
But I need to be able to do the same using a structure defined in json file.
I know we can do something like this using MvvmCross, but I dont know how to do this.
I came across these three sample solutions
a) CustomerManagement
b) CustomerManager.AutoView
Here I do see that we have a BaseCustomerEditView.cs where there is a json structure defined, but the code is commented out.
c) DialogExamples
Couple of questions
1) Whats the difference between using AutoView and MvxDialog? Is it one and the same thing?
2) Are there any potential limitations using the control structure defined in json file. I do see one problem related to alignment. We wont have any control over the alignment of controls defined in json file.
3) How to achieve rendering/binding using a structure defined in json file?
Looking forward to your responses.
Dialog provides "a foundation to create dialog boxes and show table-based information without having to write dozens of delegates and controllers for the user interface" (from https://github.com/migueldeicaza/MonoTouch.Dialog)
The idea of AutoViews is to provide a layer of auto-generated UIs built largely on top of Dialog, especially for
prototyping but also possibly for some families of apps - see Should I wait for AutoView? and http://slodge.blogspot.co.uk/2012/10/cross-platform-views-for-mvvmcross-ideas.html
The current state of AutoViews is that there is an initial implementation available on Android and iOS, and there is a small sample which shows the use of this implementation at: https://github.com/slodge/MvvmCross-Tutorials/tree/master/AutoViewExamples
The main focus of the demo uses Auto objects to describe the UI in the ViewModel - e.g.:
private KeyedDescription GetDialogAutoView()
{
var auto = new RootAuto(caption: "Some Dialog Info")
{
new SectionAuto(header: "Strings")
{
new EntryAuto(caption: "Name", bindingExpression: () => Name),
new EntryAuto(caption: "Password", bindingExpression: () => Password) { Password = true },
},
new SectionAuto(header: "Booleans")
{
new BooleanAuto(caption: "Is available", bindingExpression: () => IsAvailable),
new CheckboxAuto(caption: "Is active", bindingExpression: () => IsActive),
},
new SectionAuto(header: "DateTimes")
{
new DateAuto(caption: "Date of Birth", bindingExpression: () => DateOfBirth),
new TimeAuto(caption: "When", bindingExpression: () => PreferredContactTime),
},
new SectionAuto(header: "Debug Info")
{
new StringAuto(caption: "Name", bindingExpression: () => Name),
new StringAuto(caption: "Password", bindingExpression: () => Password),
new StringAuto(caption: "Is available", bindingExpression: () => IsAvailable),
new StringAuto(caption: "Is active", bindingExpression: () => IsActive),
new StringAuto(caption: "Date of Birth", bindingExpression: () => DateOfBirth),
new StringAuto(caption: "When", bindingExpression: () => PreferredContactTime),
},
};
return auto.ToElementDescription();
}
Instead of these Auto objects, a Json format description is available, but there's not much documentation on this today. Some example files do exist in https://github.com/slodge/MvvmCross-Tutorials/blob/master/Sample%20-%20CustomerManagement/CustomerManagement%20-%20AutoViews - for example https://github.com/slodge/MvvmCross-Tutorials/blob/master/Sample%20-%20CustomerManagement/CustomerManagement%20-%20AutoViews/CustomerManagement.Droid/Assets/DefaultViews/NewCustomerViewModel/Dialog.json
These Json files are very Reflection-based - they are built around the names and properties of the Dialog classes.
For your specific case, I think I'd recommend you don't use the AutoViews Json format.
It sounds like you have a more specific requirement - where you want the ViewModels to download Models from the web and for the ViewModels to then construct UIs from these descriptions.
Because of this I would recommend that you start by describing your own business Models in C# POCOs - these can then easily be serialised over a network connection (e.g. using Json.Net) and your ViewModels and Views can then work out how to display them.
As you already know from Is it possible to have method binding in mvvmcross using Fluent API?, you can bind your UI to a Dictionary or other 'dynamic' ViewModel structure if you want to - I suspect this will be useful when displaying and binding your dynamic form data.
It may be that you can use either the Auto or the Json parts of AutoViews as part of this last display step - but I don't think your network transfer should be done in terms of the Dialog elements - stick to business domain objects there.
Related
We have an internal API that was specifically built to be used with a new piece of software I'm building that runs on Backbone. The API has a single URL and takes JSON as input to determine what it needs to return. It essentially allows me to build custom queries with JSON that return exactly what I'm looking for.
Thing is this JSON can get pretty verbose and is often 3–4 levels deep, but sometimes may just be a few lines and just 1 level deep.
First question first: How do I send a string of JSON along with the ID when I do a fetch()? Do I have to set these parameters as the model or collection's defaults?
Here is an example of a really simple string to get a specific user's info
{
"which" : "object",
"object" : {
"type" : "customer",
"place" : "store",
"customerID" : "14"
}
}
As others have suggested it will likely be challenging to work with SOAP, but it shouldn't be impossible. Backbone models and collections communicate with the server through the sync operation; you should be able to customize that. I think something along these lines might get the ball rolling (for models):
Backbone.SoapyModel = Backbone.Model.extend({
sync: function(method, model, options) {
// force POST for all SOAP calls
method = 'create';
options = _.extend(options, {
// Setting the data property will send the model's state
// to the server. Add whatever complexity is needed here:
data: JSON.stringify({
"which" : "object",
"object" : model.toJSON()
}),
// Set the request's content type
contentType: 'application/json'
});
// Defer the rest to Backbone
return Backbone.sync.apply(this, [method, model, options]);
}
});
var SoapyModelImpl = Backbone.SoapyModel.extend({
url: '/test'
});
var soapTest = new SoapyModelImpl({
id: 42,
name: 'bob',
address: '12345 W Street Dr',
phone: '867 5304'
});
soapTest.fetch();
I have just started getting familiar with dojo and creating widgets and have a web UI which I would like to now populate with data. My question is merely to get some references or ideas on how to do this. My databases are all sql server 2008 and I usually work with microsoft.net. I thought that I would probably have to create a service that calls the sql queries and converts the results into json and feed that into the widgets whether it be the datagrid or charts. Just not sure how to do this and if it is indeed possible to do that. Any ideas appreciated.
EDIT:
store = new dojo.data.ItemFileWriteStore({
url: "hof-batting.json"
});
ngrid = new dojox.grid.DataGrid({
store: store,
id: 'ngrid',
structure: [
{ name: "Search Term", field: "searchterm", width: "10%" },
{ name: "Import Date", field: "importDate", width: "10%" }
]
}, "grid");
ngrid.startup();
I want to add data returned from my web service to this datagrid and use the same principle to add data to a chart.
Your describe exactly what you need to do.
We use C# to query our database to get the data and then convert it to json. We use multiple techniques right now for json serialization. I would recommend using JSON.NET. It is what the .NET MVC team is going to use. I would not use the DataContractSerialization that is currently part of .NET.
http://json.codeplex.com/
We sometimes put JSON right on the page and the javascript accesses it as a page variable. Other times we call services in .NET. We use WCF and we have also used an .ashx file to give the web client json data.
The structure of the json will be the contract between your dojo widgets and web server. I would use what the chart widgets or store will need to start the process of defining the contract.
EDIT
WCF Interface
[OperationContract]
[WebInvoke(Method="POST", UriTemplate = "/data/{service}/",
BodyStyle = WebMessageBodyStyle.WrappedRequest)]
String RetrieveData(string service, Stream streamdata);
The implementation returns a string that is the json. This gets sent to the browser as json, but it's wrapped by .NET by an xml node. I have a utility function that cleans it.
MyUtil._xmlPrefix =
'<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">';
MyUtil._xmlPostfix = '</string>';
MyUtil.CleanJsonResponse = function(data) {
// summary:
// a method that cleans a .NET response and converts it
// to a javascript object with the JSON.
// The .NET framework, doesn't easily allow for custom serialization,
// so the results are shipped as a string and we need to remove the
// crap that Microsoft adds to the response.
var d = data;
if (d.startsWith(MyUtil._xmlPrefix)) {
d = d.substring(MyUtil._xmlPrefix.length);
}
if (d.endsWith(MyUtil._xmlPostfix)) {
d = d.substring(0, d.length - MyUtil._xmlPostfix.length);
}
return dojo.fromJson(d);
};
// utility methods I have added to string
String.prototype.startsWith = function(str) {
return this.slice(0, str.length) == str;
};
String.prototype.endsWith = function(str) {
return this.slice(-str.length) == str;
};
I would like to know if any better way to create multiple collections fetching from a single big JSON file. I got a JSON file looks like this.
{
"Languages": [...],
"ProductTypes": [...],
"Menus": [...],
"Submenus": [...],
"SampleOne": [...],
"SampleTwo": [...],
"SampleMore": [...]
}
I am using the url/fetch to create each collection for each node of the JSON above.
var source = 'data/sample.json';
Languages.url = source;
Languages.fetch();
ProductTypes.url = source;
ProductTypes.fetch();
Menus.url = source;
Menus.fetch();
Submenus.url = source;
Submenus.fetch();
SampleOne.url = source;
SampleOne.fetch();
SampleTwo.url = source;
SampleTwo.fetch();
SampleMore.url = source;
SampleMore.fetch();
Any better solution for this?
Backbone is great for when your application fits the mold it provides. But don't be afraid to go around it when it makes sense for your application. It's a very small library. Making repetitive and duplicate GET requests just to fit backbone's mold is probably prohibitively inefficient. Check out jQuery.getJSON or your favorite basic AJAX library, paired with some basic metaprogramming as following:
//Put your real collection constructors here. Just examples.
var collections = {
Languages: Backbone.Collection.extend(),
ProductTypes: Backbone.Collection.extend(),
Menus: Backbone.Collection.extend()
};
function fetch() {
$.getJSON("/url/to/your/big.json", {
success: function (response) {
for (var name in collections) {
//Grab the list of raw json objects by name out of the response
//pass it to your collection's constructor
//and store a reference to your now-populated collection instance
//in your collection lookup object
collections[name] = new collections[name](response[name]);
}
}
});
}
fetch();
Once you've called fetch() and the asyn callback has completed, you can do things like collections.Menus.at(0) to get at the loaded model instances.
Your current approach, in addition to being pretty long, risks retrieving the large file multiple times (browser caching won't always work here, especially if the first request hasn't completed by the time you make the next one).
I think the easiest option here is to go with straight jQuery, rather than Backbone, then use .reset() on your collections:
$.get('data/sample.json', function(data) {
Languages.reset(data['Languages']);
ProductTypes.reset(data['ProductTypes']);
// etc
});
If you wanted to cut down on the redundant code, you can put your collections into a namespace like app and then do something like this (though it might be a bit too clever to be legible):
app.Languages = new LanguageCollection();
// etc
$.get('data/sample.json', function(data) {
_(['Languages', 'ProductTypes', ... ]).each(function(collection) {
app[collection].reset(data[collection]);
})
});
I think you can solve your need and still stay into the Backbone paradigm, I think an elegant solution that fits to me is create a Model that fetch the big JSON and uses it to fetch all the Collections in its change event:
var App = Backbone.Model.extend({
url: "http://myserver.com/data/sample.json",
initialize: function( opts ){
this.languages = new Languages();
this.productTypes = new ProductTypes();
// ...
this.on( "change", this.fetchCollections, this );
},
fetchCollections: function(){
this.languages.reset( this.get( "Languages" ) );
this.productTypes.reset( this.get( "ProductTypes" ) );
// ...
}
});
var myApp = new App();
myApp.fetch();
You have access to all your collections through:
myApp.languages
myApp.productTypes
...
You can easily do this with a parse method. Set up a model and create an attribute for each collection. There's nothing saying your model attribute has to be a single piece of data and can't be a collection.
When you run your fetch it will return back the entire response to a parse method that you can override by creating a parse function in your model. Something like:
parse: function(response) {
var myResponse = {};
_.each(response.data, function(value, key) {
myResponse[key] = new Backbone.Collection(value);
}
return myResponse;
}
You could also create new collections at a global level or into some other namespace if you'd rather not have them contained in a model, but that's up to you.
To get them from the model later you'd just have to do something like:
model.get('Languages');
backbone-relational provides a solution within backbone (without using jQuery.getJSON) which might make sense if you're already using it. Short answer at https://stackoverflow.com/a/11095675/70987 which I'd be happy to elaborate on if needed.
I found the very nice demo by Oleg (http://www.ok-soft-gmbh.com/jqGrid/FillToolbarSearchFilter.htm) which shows a "jqGrid toolbar search with autocomplete using local data" but have trouble to get this to work for json via ajax. Is there a good reason why the autocomplete feature won't work - even if I force the grid to be local after loading?
$(document).ready(function() {
var mygrid = $("#mylist"),
mygetUniqueNames = function(columnName) {
var texts = mygrid.jqGrid('getCol',columnName), uniqueTexts = [],
textsLength = texts.length, text, textsMap = {}, i;
for (i=0;i<textsLength;i++) {
text = texts[i];
if (text !== undefined && textsMap[text] === undefined) {
// to test whether the texts is unique we place it in the map.
textsMap[text] = true;
uniqueTexts.push(text);
}
}
return uniqueTexts;
};
mygrid.jqGrid({
url:'autocompleteTest.php',
datatype: "json",
colNames:['name', 'City','stateCd'],
colModel:[
{name:'name',index:'name',width:225, search: true},
{name:'City',index:'City',width:125},
{name:'stateCd',index:'stateCd',width:75},
],
rowNum: 100,
loadonce : true,
sortname: 'name',
sortorder: 'desc',
sortable: true,
viewrecords: true,
rownumbers: true,
sortorder: "desc",
ignoreCase: true,
pager: '#mypager',
height: "auto",
caption: "How to use filterToolbar better with data from server"
}).jqGrid('navGrid','#mypager',
{edit:false, add:false, del:false, search:false, refresh:false});
mygrid.jqGrid('setColProp', 'name',
{
searchoptions: {
sopt:['bw'],
dataInit: function(elem) {
$(elem).autocomplete({
source:mygetUniqueNames('name'),
delay:0,
minLength:0
});
}
}
});
mygrid.jqGrid('filterToolbar',
{stringResult:true, searchOnEnter:true, defaultSearch:"bw"});
});
It is difficult to provide an example in case of the usage of remote source parameter of jQuery UI Autocomplete. The main problem is that your question is about jqGrid which is pure JavaScript solution. If we would discuss the server part of tha solution we would have too options. Many users uses different languages: Java, C#, VB, PHP and so on. For example I personally prefer C#. Then we would have to choose the technology which we use: ASP.NET MVC, WCF, ASPX web service and so on. For example I would choose WCF. Then we should define the database access technology, for example, Entity Framework, LINQ to SQL, SqlDataReader, SqlDataAdapter and so on. Let us I would choose Entity Framework and would provide you the corresponding code example, but it would help you not really if you use for example PHP and MySQL.
So I just describe which interface should have the server for the remote source parameter of jQuery UI Autocomplete without any code.
You should replace in my example the source parameter to your server url like following:
dataInit: function(elem) {
$(elem).autocomplete({
source:'yourSearchUrl.php',
minLength:2
});
}
If the user types two characters (the value can be changed by minLength option), for example 'ab' then the autocomplete will make HTTP GET request with the parameter term=ab:
yourSearchUrl.php?term=ab
your server should answer with the JSON data in the same format as for the local source. I used the string array format in my previous example. Another supported format is array of objects with label/value/both properties like
[
{
"id": "Dromas ardeola",
"label": "Crab-Plover",
"value": "Crab-Plover"
},
{
"id": "Larus sabini",
"label": "Sabine`s Gull",
"value": "Sabine`s Gull"
},
{
"id": "Vanellus gregarius",
"label": "Sociable Lapwing",
"value": "Sociable Lapwing"
},
{
"id": "Oenanthe isabellina",
"label": "Isabelline Wheatear",
"value": "Isabelline Wheatear"
}
]
read the documentation for more information.
If you need to implement more complex scenario and send some additional data to the server or convert the server response in any way you can use custom source callback function. In the case you should use source: function(request, response) {/*your implementation*/}, where the request would be an object having term property (request.term). Inside of your implementation your should make ajax request to the server manually. The response would be callback function which you should call after your custom ajax request will be finished (inside of success event handler). The response function should be called with the parameter which should be array in the same format as mygetUniqueNames returns. I recommend you to examine the source code from the jQuery Autocomplete demo.
To de able to provide unique data from one column of tabele you should just use SELECT DISTINCT SQL statement which are supported in the most databases.
I hope that my description would help you.
UPDATED: If you have the local source the solution you could find in my old answer which you already use. What you just need to do is to call the filterToolbar after the source array are filled. Because you load the data from the server your should move the call of filterToolbar inside of loadComplete. You use loadonce:true jqGrid option which switch the datatype from 'json' to 'local' after the first data loading. So you can include in the loadComplete event handler of your grid the code like the following:
var grid = $('#list');
grid({
url:'autocompleteTest.php',
datatype: 'json',
loadonce: true,
// ... other parameters
loadComplete: function(data) {
if (grid.getGridParam('datatype') === 'json') {
// build the set 'source' parameter of the autocomplete
grid.jqGrid('setColProp', 'name', {
searchoptions: {
sopt:['bw'],
dataInit: function(elem) {
$(elem).autocomplete({
source:mygetUniqueNames('name'),
delay:0,
minLength:0
});
}
}
});
mygrid.jqGrid('filterToolbar',
{stringResult:true,searchOnEnter:true,
defaultSearch:"bw"});
}
}
});
If you will need to reload the data from the server (change the datatype to 'json' and call grid.trigger('reloadGrid')) you will have to change the code above so that you first destroy the autocomplete widget with $('#gs_name').autocomplete('destroy') and then create it one more time with the same code like inside of dataInit.
Using Extjs 3+ and server side is sending back the following JSON:
{"com.klistret.cmdb.ci.pojo.QueryResponse": {"com.klistret.cmdb.ci.pojo.successful":true,"com.klistret.cmdb.ci.pojo.count":1,"com.klistret.cmdb.ci.pojo.elements":{"com.klistret.cmdb.ci.pojo.id":123,"com.klistret.cmdb.ci.pojo.name":"Mars","com.klistret.cmdb.ci.pojo.fromTimeStamp":"2010-07-08T16:38:00.478+02:00","com.klistret.cmdb.ci.pojo.createTimeStamp":"2010-07-08T16:38:00.478+02:00","com.klistret.cmdb.ci.pojo.updateTimeStamp":"2010-10-25T15:02:09.446+02:00","com.klistret.cmdb.ci.pojo.type":{"com.klistret.cmdb.ci.pojo.id":1,"com.klistret.cmdb.ci.pojo.name":"{http:\/\/www.klistret.com\/cmdb\/ci\/element\/logical\/collection}Environment","com.klistret.cmdb.ci.pojo.fromTimeStamp":"2009-08-05T11:20:12.471+02:00","com.klistret.cmdb.ci.pojo.createTimeStamp":"2009-08-05T11:20:12.471+02:00","com.klistret.cmdb.ci.pojo.updateTimeStamp":"2009-08-05T11:20:12.471+02:00"},"com.klistret.cmdb.ci.pojo.configuration":{"#www.w3.org.2001.XMLSchema-instance.type":"com.klistret.cmdb.ci.element.logical.collection:Environment","#Watermark":"past","com.klistret.cmdb.ci.commons.Name":"Mars"}}}}
The reader is setup up as follows:
var reader = new CMDB.JsonReader(
{
totalProperty : 'com.klistret.cmdb.ci.pojo.count',
successProperty : 'com.klistret.cmdb.ci.pojo.successful',
idProperty : 'com.klistret.cmdb.ci.pojo.id',
root : 'com.klistret.cmdb.ci.pojo.elements'
},
[
{name: 'Id', mapping: 'com.klistret.cmdb.ci.pojo.id'},
{name: 'Name', mapping: 'com.klistret.cmdb.ci.pojo.name'}
]
);
The store as:
var ds = new Ext.data.Store({
proxy : new Ext.data.ScriptTagProxy({
url : 'http://sadbmatrix2:55167/CMDB/resteasy/element'
}),
reader : reader
});
The reader extends the Ext.data.JsonReader as explained by http://erichauser.net/2007/11/07/more-wcf-json-and-extjs/ to remove the "com.klistret.cmdb.ci.pojo.QueryResponse" start node in the JSON returned from the server.
The extended reader never gets called. Assuming the problem is due to has fully qualified property names in the JSON object returned (ie. "com.klistret.cmdb.ci.pojo.name" rather than just "name").
Anybody use gotten around this?
We worked it out (well, mostly Matthew did) in the comments:
ScriptTagProxy needs the server to wrap the JSON data in a function call so that your local code can get access to it.
Instead of the server emitting something like:
{here:'is data}
it needs to return
somefunc("{here:'is data'}");
That way, your client-side implementaiton of somefunc() is called and can process the returned data.