I'm making a Titanium Mobile app.
It's relation with Rails JSON API.
I must make some model objects for Rails model objects. It's too annoying. (paging etc.)
I want to know javascript library that mapping javascript model class to Rails model class. (like a model in backbone.js)
I searched npm registory, but I can't find it.
If you're not set on Backbone, you could look at Knockout.js with the mapping plugin. While you still have to create classes for each model, you don't need to fully populate them. A pattern I've been using a lot lately for this:
function SubModel(data, parent){
var self = this;
ko.mapping.fromJS(data,{},this);
//Various computed items and functions to work with this model
}
function Model(data, parent){
var self = this;
ko.mapping.fromJS(data,{
subModel:{
create: function(options){
return new SubModel(options.data, self);
}
}
}, this);
//Various computed items and functions to work with this model
}
You then take the JSON you get from the service, do a new Model() and pass it the data, and Knockout will create all the various properties on that class from the JS. Any nested objects can be handled in the same manner as the SubModel mapping, down to an arbitrary depth.
In addtion, the mapping plugin also includes a toJS function, that will allow you to re-serialize a model that has been created from fromJS back to JSON.
Related
I am currently trying to implement the Model-View-Controller in my SAPUI5 / Fiori Project.
I managed to create an Instance of the Controller with: new sap.ui.core.mvc.Controller('controller.js')
This does not work for the Model (sap.ui.core.mvc does not contain a Model attribute).
Now I am searching a way to call functions of the Model from the Controller, to get my data.
I already tried this code: oObjModel = new sap.ui.mode.Model(), using this I cannot call functions from my Model.
I recommend you look at the walkthrough on the SAPUI5 documentation site. It shows how to initialize all the aspects of MVC in the correct way.
Models in SAPUI5 come in different classes to support different forms of data. For example, there is JSONModel, XMLModel, ODataModel, etc.
So to create a model, you need to first determine the specific type of model you need and use its specific constructor. For example, if you have JSON data (or simply a JavaScript object), you use the JSONModel:
var yourData = { "hello": "world" };
var oModel = new JSONModel(yourData);
Note that the above code assumes you are following the recommended way to use modules and that this code is wrapped with a sap.ui.define or sap.ui.require, where the module sap/ui/model/json/JSONModel is assigned to the variable JSONModel. The walkthrough shows this correct usage pattern. Accessing the constructor directly like the below is not recommended:
// Also probably works, but not the recommended way
var oModel = new sap.ui.model.json.JSONModel(yourData);
Your way of creating a controller is also not correct. You should preferably let the view instantiate the controller for you by providing it a controllerName, as shown in the walkthrough for Controllers.
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" controllerName="name.of.your.controller">
<!-- ... -->
</mvc:View>
If you need to manually instantiate a controller from code, use this:
Controller.create({ name: "name.of.your.controller" }).then(function(oController) {
// Do something with oController
});
This again assumes you have the module sap/ui/core/mvc/Controller linked to the variable Controller.
Before version 1.56, you can use the now-deprecated sap.ui.controller function to create controllers instead:
sap.ui.controller("name.of.your.controller", null, /*async=*/true).then(function(oController) {
// Do something with oController
});
Be aware that both of these examples load the controller asynchonously, as synchronous XHR is being globally deprecated outside of Workers, and thus the framework recommends you to use async only. In fact, the new way of loading does not even provide an option for sync loading.
Can one use differently defined models defined via require.js/AMD in a single central view? I mean separately defined models, not ones of a collection.
If yes, how they are referenced in the json part of the callback function of define() that defines that central view, in its vars, in functions of its attributes etc. There can only one this.model, right?
Is it possible to Render different templates, possibly populated by vars from those different models, conditionally, from within this single central view?
To extend my question:
Can one use differently defined collections defined via require.js/AMD in a single central view?
Can one use differently defined models defined via require.js/AMD in a single collection? (this alone could achieve first goal with referencing only this capable collection.)
Can one use differently defined models defined via require.js/AMD in a
single central view? I mean separately defined models, not ones of a
collection.
If yes, how they are referenced in the json part of the callback
function of define() that defines that central view, in its vars, in
functions of its attributes etc. There can only one this.model, right?
A View's this.model is just one way to provide a view with a model. You can also pass whatever other options you want to a view, like so:
var YourView = Backbone.View.exend({
initialize: function(options) {
this.foo = options.foo;
}
});
var modelA = new Backbone.Model();
var modelB = new Backbone.Model();
var yourView = new YourView({model: modelA, foo: modelB});
// yourView.model == modelA
// yourView.foo == modelB
Is it possible to Render different templates, possibly populated by
vars from those different models, conditionally, from within this
single central view?
Yup. For instance, here's an example if we add a render method to YourView:
render: function() {
if (this.model.get('bar')) {
this.$el.html(this.templateA(this.model.toJSON());
} else {
this.$el.html(this.templateB(this.foo.toJSON());
}
}
Can one use differently defined collections defined via require.js/AMD
in a single central view?
Yup, the same way as you pass in multiple models. It doesn't matter if you use Require; you can pass around Backbone objects/classes through it just fine.
Can one use differently defined models defined via require.js/AMD in a
single collection? (this alone could achieve first goal with
referencing only this capable collection.)
Yup, just throw all the models in to a single collection, like so:
var modelA = new ModelClass();
var modelB = new SomeOtherModelClass();
var collection = new Backbone.Collection([modelA, modelB]);
The only limit is that a Collection can only have a single model property, which means that whenever you create a new Model through the Collection (eg. via fetch or create) they will all be of that model.
(It's possible to get around even that with if you replace the model with a custom function, but you shouldn't need to do that.)
If a viewmodel is already defined, either manually or automatically with mapping plugin, is there any problem to "extend" (add properties/functions) the viewmodel later in the code by using prototype?
I'm aware of the create callback of mapping plugin, I just want to know if there are any implications of using prototype instead? The reason I'm asking is because I'm generating large parts of the viewmodels from server side code, but sometimes need to extend the viewmodel later than at initial generation.
I don't think there is any problem with this, I work with a large deep view model graph instantiated via the mapping plugin from correspondingly structured JSON and I use prototypes to define an "AbstractViewModel" with useful properties and toJSON "overrides" among other things.
There is no problem with this. Just make sure that the view responds appropriately when there's no data in that particular field in the viewModel.
There seems to be a couple ways of going about this.
For one, you can take a single object view model and utils.extend them via prototype:
ko.utils.extend(ViewModelClass.prototype, {
newPrototype1: function () { ... },
newPrototype2: function () { ... }
}
Or, you can add an extender to knockout and invoke it via the observable object itself:
(http://knockoutjs.com/documentation/extenders.html)
ko.extenders.numeric = function(target, precision) {
...details in link above...
}
...
self.NumericProperty = ko.observable(data).extend({ numeric: 0 });
...
Or create a function that is available to all instances of an observable, observableArray, computed...
(http://knockoutjs.com/documentations/fn.html)
ko.observable.fn.filterByProperty = function(propName, matchValue) {
return ko.computed(function() {
...details in link above...
}, this);
}
I tend to use combinations of these. I like extending prototypes of View Models in a separate file from the VMs and Mappings, so I have a structure like
ViewModel(s).js
ViewModel(s).Mappings.js
ViewModel(s).Prototypes.js
As you'll see, the timespan between these 'answers' is rather large, so some things have changed yet some things remain the same.
I am designing a generic object browser plugin which functions similar to OS X's Finder in column view. I have divided up the interface in to several nested views, the browser, the columns and the objects.
I will be using this plugin in several scenarios where the browser view, object view and column view may or may not need to be customised. Sometimes the objects will be files and folders for example.
This is OS X's Finder in column view in case you don't know what it looks like.
At the moment I am using RequireJS to pass around the dependencies however in order to simply inherit and extend the ObjectView, I must replace the entire stack.
Is there any better structure where the plugin can be extended but only part of?
BrowserView.js
var BrowserView = Backbone.View.extend({
open: function () {
var collectionView = new CollectionView( {collection: objects} );
}
});
CollectionView.js
var CollectionView = Backbone.View.extend({
render: function () {
this.collection.each( function (object) {
var objectView = new ObjectView( {model: objects} );
objectView.bind('click', this.select, this);
this.container.append( objectView.el );
objectView.render();
this.objectViews.push(objectView);
}, this );
},
});
ObjectView.js
var ObjectView = Backbone.View.extend({
});
I would put these views in the same module.
The purpose of a module - whether you're using RequireJS or just plain old JavaScript modules - is to encapsulate a set of related objects and functions, for a specific purpose. In this case, your purpose is the Finder View.
By keeping all of the related objects in the same file, you'll have more freedom and flexibility for how you make the objects work together.
As a side note, but related to what you're doing, you might be able to get some ideas for how to make this work from the "CompositeView" of my Backbone.Marionette plugin. I've built a hierarchical tree-view of folders and files with it before, and the column view of Finder would be fairly easy to build with it, too.
Note that I'm not suggesting you need to use my plugin. Rather, I think it might be helpful in figuring out how you want to structure your code.
I've got a blog post that talks about it here: http://lostechies.com/derickbailey/2012/04/05/composite-views-tree-structures-tables-and-more/
You can find the code and docs here: https://github.com/derickbailey/backbone.marionette
And the annotated source code for the composite view is here: http://derickbailey.github.com/backbone.marionette/docs/backbone.marionette.html#section-26
I am using Base2 as a means to allow us to easily do inheritance in our system, aswell as using KnockoutJS for some UI interactions.
We have defined a base class for our ViewModels
BaseViewModel = Base.extend({
...
});
Which we then extend for our view models:
ExampleViewModel = BaseViewModel.extend({
text: ko.observable("")
});
However there seems to be a problem. When you create 2+ instances of the view model (say if you are pushing them in to an observableArray and using templates to build up a UI) it seems like any changes made to a bound field, updates all view models rather than just the one it's bound to.
Does anybody know why this might be?
Because the extension is not actually instantiating a new observable, its just copying the reference.
I think you can do something like this:
ExampleViewModel = BaseViewModel.extend({
constructor: function() {
this.text = ko.observable("");
}
});
Not as nice though as normal Base2 syntax, but just a limitation in how Knockout is implemented due to issues with properties.