Profile feature in Sencha Touch 2 causes problems in production mode build - sencha-touch-2

I have created a Sencha Touch 2 app and built a production mode version. However, I have encountered a big issue with the production build and it running in Phone/Tablet modes.
The current profile implementation of ST2 seems flawed as even if you have a specific profile activated, all views are still loaded in. In my application I want to be able to specify views using the xtype alias in the view config, and have the correct view for phone or tablet profile loaded in without any special coding. If all views from profiles are loaded in then this can't work (one view will always override another).
The only way I could achieve this was to dynamically add the profile at bootup stage (within app.js) like so:
Ext.application({
name: 'MyTestApp',
var activeProfile = Ext.os.is.Phone ? ['Phone'] : ['Tablet'];
requires: [ ... ],
profiles: activeProfile
});
This has worked fine. It means I can then load the correct view and still just use the xtype alias within the config of another view and/or ref in a controller. However, I noticed that when I generate a production build and load up a console window, both of the following are defined:
MyTestApp.views.phone.Login
MyTestApp.views.tablet.Login
Normally the tablet or phone version would be undefined depending on the profile. I'm assuming this is the case because the production mode build has parsed ALL dependencies and then included all views regardless of the profile.
So in my start-up controller I have a button handler which then creates a login view from the xtype.
Controller:
refs: {
loginView: {
selector: 'loginview',
xtype: 'loginview',
autoCreate: true
}
}
Handler:
var loginView = this.getLoginView();
In development mode, the loginView variable will either be MyTestApp.views.tablet.Login or MyTestApp.views.phone.Login depending on the profile.
How do I ensure that the loginview instantiated here gets the correct version depending on the profile when in production mode?

I had been struggling with this, when I would move either of the solutions to the devices, I would be stuck with the fact that all views are referenced and would get some xtype collision always giving me the phone view. ( i had to move to aliases eventually - not sure why :( ). I finally managed to crack this for my use case, just sharing for future reference.
I am running touch 2.3.1 and cordova 3.3.1 with the latest cmd 4.0.2.67
I use the solution from Christopher except I had to change the source code in the sencha touch source directory rather than keep it in the app.js [truthfully I don't know why it hangs when I leave it as an override]
In addition I have had to configure the views the following way in order for:
define a base class for the view with an alias so the controller to understand the ref as it loads first
dynamically assign the alias to the view instantiated by the profile
strip out (using Christopher code)
Base class for the views
Ext.define('MyApp.view.CatalogView', {
extend: 'Ext.Container',
alias: 'widget.catalogview'
});
Assign an alias to the profile specific view
Ext.define('MyApp.profile.Phone', {
extend: 'Ext.app.Profile',
config: {
name: 'Phone',
views: ['CatalogView'],
},
isActive: function() {
return Ext.os.is('Phone');
},
launch: function() {
Ext.ClassManager.setAlias('MyApp.view.phone.CatalogView', 'widget.catalogview');
}
});
Repeat for the tablet view

For all who want to know how I resolved this, I'm now left bald after pulling all my hair out;)
All my profile views where I want to have the xtype names remain the same even though they might belong in the phone or tablet profiles, I have to remove the alias/xtype config on the class. I then have a profile base class defined like so with a shared helper function:
Ext.define('MyApp.profile.Base', {
extend: 'Ext.app.Profile',
config: {
},
mapViewAliases: function () {
var self = this;
var views = this.getDependencies().view;
var newAliasMap = null;
Ext.each(views, function (view) {
Ext.Array.some(self.getViewsToAliasMap(), function (map) {
if (map[view]) {
if (!newAliasMap) {
newAliasMap = {};
}
newAliasMap[view] = [map[view]];
return true;
}
});
});
if (newAliasMap) {
console.log('view aliases being mapped for: ' + this.$className);
Ext.ClassManager.addNameAliasMappings(newAliasMap)
}
}
});
Then I have the profile class inherit from the base class (this is repeated with the tablet profile except the viewsToAliasMap holds classes belonging to the tablet profile instead of the phone profile):
Ext.define('MyApp.profile.Phone', {
extend: 'MyApp.profile.Base',
config: {
name: 'Phone',
views: ['Login', 'Home', 'Welcome' ],
viewsToAliasMap: [
{ 'MyApp.view.phone.Login': 'widget.loginview' },
{ 'MyApp.view.phone.Home': 'widget.homeview' },
{ 'MyApp.view.phone.Welcome': 'widget.welcomeview' }
]
},
isActive: function () {
return Ext.os.is.Phone;
},
launch: function () {
console.log("Phone profile launched");
this.mapViewAliases();
}
});
So basically, the profile calls the function mapViewAliases() on the base class in the launch function. The mapViewAliases() registers the view class names with the aliases defined in the profile with the class manager. So effectively the xtype names are resolved at run-time.
I'm sure this code can be improved and/or a better way to do this.
Please feel free to let me know.

I am using a pretty naive implementation... I'm sure it could be made more robust, but I've been hacking at this for 5 hours or so now.
Ext.define('MyApp.override.Application', {
override : 'Ext.app.Application',
onProfilesLoaded: function() {
var profiles = this.getProfiles(),
length = profiles.length,
instances = [],
requires = this.gatherDependencies(),
current, i, profileDeps;
for (i = 0; i < length; i++) {
var instance = Ext.create(profiles[i], {
application: this
});
/*
* Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
* a single build file that will work on all defined Profiles. Although the other classes will be loaded,
* the correct Profile will still be identified and the other classes ignored. While this feels somewhat
* inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
* the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
* load Profile-specific builds in a future release.
*
CMK - PSHAW!
*/
if (instance.isActive() && !current) {
console.log('Profile active: ' + instance.getName());
current = instance;
profileDeps = instance.getDependencies();
requires = requires.concat(profileDeps.all);
var ns = instance.getNamespace();
this.setCurrentProfile(current);
// Merge Controllers, Models, Stores, and Views
this.setControllers(this.getControllers().concat(profileDeps.controller));
this.setModels(this.getModels().concat(profileDeps.model));
this.setStores(this.getStores().concat(profileDeps.store));
this.setViews(this.getViews().concat(profileDeps.view));
// Remove the view ref and requires for default views, when a profile specific one exists
Ext.each(profileDeps.view, function(className) {
if (className.indexOf('view.' + ns + '.') !== -1) {
// Requires
var index = requires.indexOf(className.replace('view.' + ns, 'view'));
if (index !== -1) {
requires.splice(index, 1);
}
// Views
index = this.getViews().indexOf(className.replace('view.' + ns, 'view'));
if (index !== -1) {
this.getViews().splice(index, 1);
}
}
}, this);
instances[0] = instance;
break;
}
}
this.setProfileInstances(instances);
Ext.require(requires, this.loadControllerDependencies, this);
}
});
Put this before your Ext.application, and it replaces the profile loader... This one strips out default views with the same name as one in the active profile namespace.
It requires that you define an xtype for the views that match, then even your refs in controllers will work...
I need to continue testing with this, but it looks promising so far.

Related

Cumulocity - Custom widget configuration

I'm writing a chart widget in cumulocity platform.
In widget comes with the platform,
I can select data point after I select device:
But the widget I wrote can only select device, there is no data point option for me to select:
I know there is c8yComponentsProvider that has options for me to select if I want device target or not. Is there a way for me to choose what data point I want?
From angular 2 it has to be done in this way.
In my custom widget project I added to the usedValue secion of app.module.ts as follows.
useValue: {
id: 'acme.text.widget', // 3.
label: 'Text widget',
description: 'Can display a text',
component: WidgetDemo, // 4.
configComponent: WidgetConfigDemo,
data: {
ng1: {
options: {
noDeviceTarget: true
}
}
}
}
You can disable the device selector in the options of the c8yComponentsProvider:
options: {
noDeviceTarget: true
}
And then use the following directive in your widget config html:
<c8y-data-point-list datapoints="data.datapoints"></c8y-data-point-list>
You need to set the data points to choose in the data.datapoints object on the widget configuration controller. Therefore you can search for managed objects with the fragment c8y_DataPoint.
In the document is an example how to do that with the c8yInventory service:
var filters = {fragmentType: 'c8y_DataPoint', withParents: true};
$scope.data = {};
c8yInventory.list(filters).then(function (devices) {
$scope.data.datapoints = [];
_.forEach(devices, function(dp) {
$scope.data.datapoints.push(dp);
});
});
Note that the c8y-data-point-list is a nonofficial directive. If you face any problems or you want a specific look, you might be faster by writing your own directive.

What is the proper way of adding a custom field to Keystone (to be included in an admin UI form)?

I can see the nice explanation for fields, and what they are made of, here: https://github.com/keystonejs/keystone/tree/v4.0.0-beta.5/fields
How do you go about adding a custom field?
Is adding a custom field (versioned in my own project which depends on keystone, or perhaps done generic enough that could be pushed to npm) a matter of importing it during the keystone setup script and somehow mutating the keystone instance or whatever in order that it also loads my field along with the built-in ones?
EDIT:
The use case is in the context of the admin UI (e.g. you have a User keystone model, and you want the User form to have a new custom field whose UI is an arbitrary react component you implement)
The framework does support storage fields like local file, s3, azure, cloudinary images and embedly fields. That might satisfy your file field needs.
Custom Fields
It appears that the keystone wiki has a short tutorial on the keystonejs wiki and at time of writing, custom types aren't supported in the admin UI.
The example code in the wiki includes a validation method for a credit card number, so this might be the type of functionality that you're looking for.
Here's a short example of what a custom type would look like. It's a field that only accepts Jeff or Alexander as a valid value. You would put it in its own myNameType.js file.
var keystone = require('keystone');
var util = require('util');
/*
Custom FieldType Constructor
#extends Field
#api public
*/
function myName(list, path, options) {
// add your options to this
// call super_
this._nativeType = Text;
myName.super_.call(this, list, path, options);
}
/* inherit Field */
util.inherits(myName, keystone.Field);
/* override or add methods */
myName.prototype.validateInput = function(data) {
console.log('validate my name');
var isValid = false;
if (data && (data.toLower() === 'jeff' || data.toLower() === 'alexander')) {
isValid = true;
}
return isValid;
};
Then register your type in the keystonejs startup file:
// Require keystone
var keystone = require('keystone');
// add a new custom Field Type
Object.defineProperty(
keystone.Field.Types,
'MyName',
{
get: function() {
// or whatever your path is
return require('./myName.js');
}
}
);
From there you can use it in a model (remember to set it to hidden because of the lack of admin UI support):
var keystone = require('keystone');
var Types = keystone.Field.Types;
var Person = new keystone.List('Post', {
map: { name: 'title' },
autokey: { path: 'slug', from: 'title', unique: true },
sortable: 'unshift',
perPage: 5,
track: true,
autocreate: true
});
Person.add({
name: { type: Types.MyName, label: 'My Name', hidden: true },
heightInInches: { type: Types.Number, label: 'Height (inches)' },
});
Person.register();

Unable to get Store inside Sencha Controller

I'm using Sencha Touch 2.3. I'm trying to get a Store instance inside a controller in a similar way thats defined in this article http://www.sencha.com/learn/architecting-your-app-in-ext-js-4-part-3/.
I've defined the 'Location' store in the Controller config. I then try to get the store using 2 methods that both fail. First through Ext.getStore and the second through getLocationStore which should be an autogenerated function. Both fail. The first call returns undefined and the second call throws an exception because the function is not available.
Ext.define('MyApp.controller.Location', {
extend: 'Ext.app.Controller',
config: {
refs: {
locationSearchField: '#locationSearchField'
},
control: {
locationSearchField: {
action: 'onSearchAction'
}
},
stores: [ 'Location' ]
},
onSearchAction: function() {
var locationSearchStore = Ext.getStore('Location');
if (locationSearchStore == undefined) {
Ext.Logger.warn('Could not locate locationSearchStore');
locationSearchStore = this.getLocationStore();
if (locationSearchStore == undefined)
Ext.Logger.warn('Could not location locationSearchStore again!');
else
Ext.Logger.info('Success!');
}
else
Ext.Logger.info('Success!');
}
});
You can get your store by: Ext.data.StoreManager.lookup('Location') (if it's called MyApp.store.Location).
To be sure, that you are in the right context in the onSearchAction, try to call console.dir(this); and check that this is the controller object itself
First of all, you want to access store in sencha touch but you have given link of extjs. Second, you need to define your store first and then add it in app.js file. And then you can access your store by Ext.getStore('Location') method. For reference you shold learn this http://miamicoder.com/2012/sencha-touch-2-stores-adding-removing-and-finding-records/

Accessing properties of ember-data through relationship (Not in the template)

I want to stress that this problem only occurs outside of a template, such as when I try to access properties of related objects while in a controller, unit test, etc. Rendering the template seem to get the property well and work as expected.
Here is a simple example in JS Bin with a failing test http://jsbin.com/ihumuk/4/edit which repros my problem. The passing test asserts that the property is accessible and rendered in the template as expected. The failing test shows that I get null when I try to access the property with get. Really nothing fancy here but I don't understand why it's returning null.
Here is the application part of the JS Bin example:
App.ApplicationRoute = Em.Route.extend({
model: function() {
return App.Foo.find();
}
});
App.Store = DS.Store.extend({
adapter: DS.FixtureAdapter.create()
});
App.Foo = DS.Model.extend({
name: DS.attr("string"),
/**
* The subject under test
*/
childName: function() {
return this.get("child.name");
}.property("child.name"),
child: DS.belongsTo("App.Bar")
});
App.Bar = DS.Model.extend({
name: DS.attr("string")
});
App.Foo.FIXTURES = [{
id: 1,
name: "Fred",
child: 3
}, {
id: 2,
name: "Barney",
child: 4
}];
App.Bar.FIXTURES = [{
id: 3,
name: "Pebbles"
}, {
id: 4,
name: "Bam Bam"
}];
This passes.
test("Child name is rendered", function() {
expect(1);
visit("/").then(function() {
ok(find("div:contains(Pebbles)").length);
});
});
This fails.
test("Child name is accessed", function() {
expect(2);
var foo = App.Foo.find(1);
equal(foo.get("childName"), "Pebbles");
equal(foo.get("child.name"), "Pebbles");
});
This has to be something simple/stupid like forgetting a character or something, but I think I've driven myself too far into frustration to think clearly for a while. Thanks in advance for any help.
You need to use the then to know when the data is loaded
asyncTest("Child name is accessed", function() {
expect(2);
// load the data from server
App.Foo.find(1).then(function(foo) {
// the child id is 3, we need to fetch the remaining data
// and this is async, because of the ajax request
foo.get("child").then(function(child) {
equal(child.get("name"), "Pebbles");
// childName call child.name, but since the
// data is loaded, isn't necessary to use a second then
equal(foo.get("childName"), "Pebbles");
start();
});
});
});
In ember data, like major of the orm's, the data is lazy loaded, for relationships. This is because, isn't needed to return all loaded object graph, let's leave the user ask for what it want, and then load.
Because some implementations are async, like: websql, indexeddb, ajax, websockets etc. The interface of ember-data is async, so you need to use the then method to know when the data is loaded or failed.
The things work in your template, because it are binding aware. Even when the change are async, it will be finished later, and the bindings will be notified and updated.
I have updated your demo, and the tests pass http://jsbin.com/eqojaj/1/edit

view is undefine, loading combo box issue in 4.07

I am occassionally ( usually 1 in 3 page loads) receive the following error message
view is undefined
view.onItemSelect(record);
In my view
{
xtype:'combobox',
name:'PurchaseOrderStatusId',
id:'PurchaseOrderStatusCombo',
displayField:'Name',
store:'PurchaseOrderStatuses',
mode:'local',
valueField:'Id',
fieldLabel:'Status',
width: 350
},
{
xtype:'combobox',
name:'SupplierId',
id:'SupplierCombo',
displayField:'Name',
store:'Suppliers',
mode:'local',
valueField:'Id',
fieldLabel:'Suppliers',
width: 350
},
// in my controller
onLaunch: function () {
var suppliers = this.getSuppliersStore();
suppliers.load();
var purchaseOrderStatuses = this.getPurchaseOrderStatusesStore();
purchaseOrderStatuses.load();
var purchaseOrdersStore = this.getPurchaseOrdersStore();
purchaseOrdersStore.load({
callback: this.onPurchaseOrderLoad,
scope: this
});
},
onPurchaseOrderLoad: function (selection) {
var form = Ext.getCmp('purchaseOrderForm');
form.loadRecord(selection[0]);
},
in my model
{
mapping:'PurchaseOrderStatusId',
name:'PurchaseOrderStatusId'
},
{
mapping:'SupplierId',
name:'SupplierId'
}
It's not clear how the error message you are reporting is linked to any of the code you have revealed so far. So it's difficult to connect the dots here.
However here are a couple of observations:
1. If you need your stores to always load ASAP when your app launches set the autoLoad:true config on those stores. This way you don't have to explicitly load them and they have more time to finish loading before your view is ready to go.
2. If you need to load an instance of your model into the form you can use Model.load method instead of having a store do that for you. You will need to provide API on the model on how to read the records from the server.
3. Model mapping is not necessary if your Model fields match to what the server returns.
If you need further help, please update your question with some more debug info.