JS Object strange behaviour when trying access Loopback related model query - javascript

I am working with the Loopback Framework, doing a web project.
But I think that the question that I am exposing here has less to do with this, but with general Javascript / Node.JS knowledge.
At one part of the code, I am doing:
roleMapping.find({
where: {
principalType: 'USER',
principalId: context.principals[0].id
},
include: 'role'
}, function(err, roles){
console.log(roles[0]);
for (var i in roles)
{
if (roles[i].role.name === 'teamLeader' &&
roles[i].groupId === context.modelId)
{
cb(null,true);
}else {
cb(null,false);
}
}
});
Ok with this, but it fails when trying to compare roles[i].role.name.
So, I went logging what the roles[i] object contained.
{ groupId: 1,
id: 3,
principalType: 'USER',
principalId: 1,
roleId: 2,
role:
{ id: 2,
name: 'teamLeader',
description: 'The leader(s) of a team',
created: null,
modified: null } }
Ok, nothing wrong, but it still fails, so I tried to print just the role property. And to my surprise:
{ [Function]
update: [Function],
destroy: [Function],
create: [Function],
build: [Function],
_targetClass: 'Role' }
So, the role property seems to be some sort of function? But how it was been correctly printed before?
Eventually, lost in my frustration I tried var role = JSON.parse(JSON.stringify(roles[i]));
And then I could access every property of the object normally, but this is not clean nor normal.
This blew my mind for the first time in years of JS programming (sort of amateurish though), and I would be pleased if someone could clarify this to me. Thanks
EDIT: It seems that it is specific to this Framework, so I'm changing title to help community.

I just found issue 1425 which links to the following docs:
With Node.js API, you need to call toJSON() to convert the returned model instance with related items into a plain JSON object
Please note the relation properties […] points to a JavaScript function for the relation method.
So it seems you have to use
for (var i=0; i<roles.length; i++) {
var x = roles[i].toJSON();
cb(null, x.role.name === 'teamLeader'
&& x.groupId === context.modelId);
}

Related

Upgrading ExtJS in old ASP.NET application from 2.3 to 6

Looking for some assistance. TLDR version: we have an ASP.NET web app that leverages ExtJS 2.3 and we are looking to upgrade to the current ExtJS version. Trying to get my head around what we’re in for.
Now for the details. I will preface by saying that I am not an expert in ExtJS nor .NET development. In fact, I’m a novice pretty much across the board when it comes to web development, so please excuse any poor explanations or misuse of terms on my part. My team is developing a web app on a “custom” framework that was developed a number of years ago at our company. It’s based on some re-runnable code generation tools that take xml templates and spit out the necessary code files. Our project is an ASP.NET MVP application that uses .aspx pages and NHibernate for ORM. Our UI is created from ExtJS—the controls are defined in each page’s .js file and then “assembled” in the .aspx page. The codebehind contains web methods that leverage the presenter of the C# code. I’ve included a snippet to demonstrate what I’m talking about below.
.aspx page:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Entity.aspx.cs" Inherits="View.Example.EntityView" MasterPageFile="~/MasterPages/Content.Master" %>
<asp:Content ID="Content1" runat="server">
<script language="javascript" type="text/javascript" src="~/Scripts/ext-2.2.1/ext-all.js"></script>
<script language="javascript" type="text/javascript" src="<%=ResolveUrl("~/Scripts/Factory/Example/Entity.js")%>"></script>
<script language="javascript" type="text/javascript">
var localConfig = new panelConfig();
localConfig.applyExtendedConfig('default_page');
localConfig.addItem(new Ext.grid.GridPanel(pageConfigs.default_page_ManageEntity));
localConfig.addItem(
new Ext.form.Hidden({
id: 'ManageEntityGrid_Rows'
}));
var default_page = localConfig.createExt();
default_page.on('render', default_page_OnShow, default_page, { single: true });
</script>
</asp:Content>
.js file:
var get_manageEntity_columns = function() {
var columns = [
{ header: "Name"
,id: 'ManageEntity-col-Name'
, dataIndex: 'Name'
, sortable: true
},
{ id: 'ManageEntity-col-ActiveFlag'
, header: 'Active Flag'
, dataIndex: 'ActiveFlag'
, hidden: true
,tags: []
, sortable: true
},
{ id: 'ManageEntity-col-CreatedTimestamp'
, header: 'Created Timestamp'
, dataIndex: 'CreatedTimestamp'
, hidden: true
,tags: []
, renderer : formattedDateTime
, sortable: true
},
{ id: 'ManageEntity-col-Id'
, header: 'Entity ID'
, dataIndex: 'Id'
, hidden: true
,tags: []
, sortable: true
}
];
return columns;
}
var get_grid_reader_manageEntity = function(custom_fields) {
var fields = [
{ name: 'ActiveFlag', mapping: 'ActiveFlag' },
{ name: 'CreatedTimestamp', mapping: 'CreatedTimestamp' },
{ name: 'Id', mapping: 'Id' },
{ name: 'Name', mapping: 'Name' }
];
if (custom_fields) {
fields = fields.concat(custom_fields);
}
return new Ext.data.JsonReader({
root: 'Results',
totalProperty: 'Total',
id: 'Id'
}, fields);
}
var get_grid_datastore_manageEntity = function() {
var store = new Ext.data.Store({
proxy: new Ext.data.PageMethodProxy({
pageMethod: 'GetManageEntity'
}),
reader: get_grid_reader_manageEntity()
, remoteSort: true
});
store.loadOrReload = function() {
if (store.rapidLoaded)
store.reload();
else
{
store.rapidLoaded = true;
store.load({ params: { start: 0, limit: gPageSize }
});
}
}
get_grid_datastore_manageEntity = function() { return store; };
return store;
}
var pageConfigs = {
default_page_ManageEntity: {
store: get_grid_datastore_manageEntity(),
columns: get_manageEntity_columns(),
viewConfig: {
forceFit: true
},
sm: get_manageEntity_sm(),
layout:'fit',
frame: true,
id: 'ManageEntity',
plugins: [
grid_filters_manageEntity
],
iconCls: 'icon-grid',
loadMask: true,
stripeRows: true,
bbar: get_grid_paging_toolbar_manageEntity(),
listeners: {
rowcontextmenu: show_grid_menu_manageEntity
,bodyscroll: function() {
var menu = get_grid_menu_manageEntity();
if (menu.isVisible()) menu.hide();
}
,headerClick: function() {
this.getStore().on('beforeload', this.saveState, this, { single: true });
}
,render: function(){
var grid = this;
Ext.onReady(function() {
add_applied_filters(grid);
var grid_state = Ext.state.Manager.get('ManageEntity') || {};
if (!grid_state.default_filter_applied) {
var filters = grid_filters_manageEntity;
var activeflag_filter = filters.getFilter("ActiveFlag");
activeflag_filter.setValue(["", new Array("1")]);
activeflag_filter.setActive(true);
grid.on('beforestatesave', function(grid, state) { state.default_filter_applied = true; });
}
grid.getStore().load({ params: { start: 0, limit: gPageSize }
});
});
}
}
}}
.aspx.cs file:
[WebMethod()]
public static ExtJSGridData GetManageEntity(PageProxyArgs args)
{
var watch = new Stopwatch();
watch.Start();
try
{
var data = new ExtJSGridData();
var criteria = GetManageEntityQuery(args);
criteria.SetFirstResult(args.Start).SetMaxResults(args.Limit);
data.Results = GetDataManageEntity(args.RecordId, criteria);
criteria.SetFirstResult(0).SetMaxResults(RowSelection.NoValue);
criteria.ClearOrders();
data.Total = criteria.SetProjection(Projections.CountDistinct("Id")).UniqueResult<int>();
data.UserUiStateSaved = UserUiStateHelper.SaveUserUiStateInTransaction(args.UserUiState);
watch.Stop();
PageLogHelper.CurrentLog.ServerTime = watch.ElapsedMilliseconds;
return data;
}
catch (Exception ex)
{
LogManager.GetLogger((MethodBase.GetCurrentMethod().DeclaringType)).Error(ex);
ErrorHandler.LogError(ex);
throw;
}
}
private static IList GetDataManageEntity(int id, ICriteria criteria)
{
var list = criteria.List<Model.BusinessObjects.Entity>();
var jsonList = Model.BusinessObjects.Entity.ToJSON(list);
return jsonList;
}
private static ICriteria GetManageEntityQuery(PageProxyArgs args)
{
ICriteria criteria = StaticPresenter.GetEntity();
var helper = new GridFilterHelper(criteria, args, _dManageEntityLookupSortInfo);
helper.ApplyFilterMap(EntityJSON.GetGridFilterMap(criteria, args.Filters));
MapManageEntityFilters(args.Filters, criteria);
helper.ApplyFilters();
if (args.SortInfo == null || string.IsNullOrEmpty(args.SortInfo.FieldName))
return criteria;
IList<IProjection> sortMap = StaticPresenter.GetSortMap_ManageEntity(args.SortInfo.FieldName, args.RecordId, args.ExtraParams, criteria);
if (sortMap == null)
sortMap = EntityJSON.GetSortMap(args.SortInfo.FieldName, criteria);
helper.ApplySort(sortMap);
return criteria;
}
So, here is where the question comes in. As mentioned, the version of ExtJS we’re using is 2.3, and we’re looking to upgrade to the current version. I’ve done some initial homework of googling and looking through the sencha documentation, but there are some things which I’m unclear on and would like to get addressed before I start getting hands on with this effort. I’ve tried to outline my specific questions below.
First and foremost: Is the way our application is built even possible with ExtJS 6? By this, I mean leveraging the ExtJS API to define controls in the .js file and then create a UI on top of a .NET C# backbone. Based on the change notes and questions from other users, it’s pretty apparent that there have been massive (understatement) changes between 2.3 and 6. I guess what I’m getting at is that based on what I’ve read it seems you can now build your entire app, including the model and view (and controller?) in ExtJS. Is this a requirement, or can we still lay ExtJS controls on top of our .NET C# model and view?
As a follow up, I’ve been seeing references about Sencha CMD to create and build the app etc. Is cmd going to be required no matter what? Or can we simply reference the ext js library like we’re currently doing?
Assuming the answer to question 1 is yes it’s possible, the next obvious question becomes: how much work is this going to be? Let’s get the “a lot” answer out of the way—I know. What I do know is that we will have to update all of our templates to use the new API syntax (new Ext… to Ext.create() etc). I’m okay with this. What I’m trying to figure out is what I don’t know. Assuming I update all of the syntax, would our application work? Or are there other things I need to change/fix in order to get it working?
Related to question 2: based on my reading it looks like the way data stores for controls has changed and they now use the model defined in ExtJS. Is this a requirement? As described earlier, we’re currently using web methods in the aspx.cs file. Am I going to need to duplicate our C# model in ExtJS?
Lastly, I see this asked a lot but I can’t seem to find a definitive answer. Classic vs modern? The answer I typically see is that modern is aimed more towards touch screens and modern browsers, while classic is more geared toward desktop users. I’ve also read in places that modern has fewer controls available. Our web app is running in a local environment and will not be going to mobile in the future, which leads me to think classic might be the right choice? I guess I’m just wondering technically what the difference is.
I’m sure there are things I don’t even know I’m missing. Any and all feedback is welcome.
It is possible, but you will have to do a lot handwriting. Just three weeks ago I had to leverage a 3.4 ASP to 6.2.1
You can either set the variables to a global variable and on start add these to the mainView ViewModel or load them right away onBeforeLaunch.
Then code your app and build it using Sencha CMD. At the end add all together in your ASP stuff.
About how much work ... depends a lot on how structured your code is, how easy it will be to rewrite the code.
Let's pretend it is written in the same style all over the application, then it will be relatively easy.

Nested JSON Response coming back as [Object] using FB.API?

I know this is something really simple but for the life of me I can't figure out how to parse out the next level json request from this bit of javascript I'm writing in Node.js using the Facebook API.
Code:
for(var i = 0; i < userArrayLength; i++) {
fb
.api(usersArray[i] + '/posts?since=2017-05-17', { fields: ['from', 'id'], access_token: 'accessToken' }, function (res) {
console.log(res, {depth: null});
});
}
;
My return has the following:
from: [Object],
id: 'postid_xxxxxxxxxxxxxxxx',
I know that all I need to do is step down on level in the JSON to from:name but I just can't get the formatting correct and can't find any good examples online.
I'm new to node and javascript so I'm sure its just something boned headed. Would appreciate any help!
Thanks!

Sequelize: .createAssociation() or .setAssociation doesn't update the original object with created data

I've been stuck on this for a while. Take the following code as an example:
models.Summoner.findOne({
include: [{ model: models.RankedStats, as: 'SummonerRankedStats', required: true }],
where: { summonerId: summonerId, server: server }
}).then(function(summoner) {
models.RankedStats.create({
totalWins: 0,
totalLosses: 0
}).then(function(rankedStats) {
summoner.setSummonerRankedStats(rankedStats).then(function() {
console.log(summoner.SummonerRankedStats)
//This outputs undefined
summoner.getSummonerRankedStats().then(function(srs) {
console.log(srs)
//This outputs the RankedStats that were just created
})
models.Summoner.findOne({
include: [{ model: models.RankedStats, as: 'SummonerRankedStats', required: true }],
where: { summonerId: summonerId, server: server }
}).then(function(summoner) {
console.log(summoner.SummonerRankedStats)
//This outputs the SummonerRankedStats object
})
})
})
})
So, to put it simply... If I have a Summoner (var summoner) and perform a .setAssociation() or .createAssociation() on it, and then log summoner, the data created isn't there. If I fetch it again from the database (with .getAssociation() or by searching for that Summoner again) I can access it, but I was hoping to avoid that extra DB call.
Is there a way to add this information to the original object when using .create() or .set()? It can be achieved by doing something like:
summoner.dataValues.SummonerRankedStats = rankedStats
But that seems somewhat hacky :)
Is there a correct way to do it, or does it even make any sense?
Thanks in advance!

sequelize saving multiple objects that assosiates

I wasnt quite sure what to call this question but here is my setup:
var AcademyModule = sequelize.define('academy_module', {
academy_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
module_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
module_module_type_id: DataTypes.INTEGER,
sort_number: DataTypes.INTEGER,
requirements_id: DataTypes.INTEGER
}, {freezeTableName: true,}
With the following assosiation:
Requirements = sequelize.define('requirements', {
id: DataTypes.INTEGER,
value: DataTypes.STRING,
requirement_type_id: DataTypes.INTEGER
}, {freezeTableName: true});
AcademyModule.belongsTo(Requirements, {foreignKey: 'requirements_id'});
Now as you can see from my table setup i would have to save a row in requirements table and then use the inserted id to insert into the academy_module table.
for this i created the following:
add: function (requirements,academyModule,onSuccess, onError) {
var academyModule = academyModule;
if(requirements == null) {
AcademyModule.build(this.dataValues)
.save().ok(onSuccess).error(onError);
} else {
if(requirements.requirement_type_id == '1') {
requirements.value = requirements.module.id;
}
Requirements.create(requirements).then(function(createdReq) {
var associationPromises = [];
associationPromises.push(AcademyModule.create(this.dataValues));
return sequelize.Promise.all(associationPromises);
}).success(onSuccess).error(onError);
}
}
However in the then function i am unable to reach the academyModule object that contains the values that needs to be inserted.
This is a repeating problem for me and i really want to know how it is possible to connect so they do it automatically without doing small hacks
i have scouted the documentations but i havnt been able to find a single example of the above (which i find rather odd seeing as this is a fairly normal situation)
Jan's method
I tried to solve it using Jan's elegant method
However i am getting an error saying:
academy_module is not associated to requirements!
My code looks like this as for now:
var academyModule = academyModule;
if(requirements == null)
{
AcademyModule.build(this.dataValues)
.save().ok(onSuccess).error(onError);
}
else
{
requirements.academy_module = academyModule;
Requirements.create(requirements, {
include: [AcademyModule]
});
}
Funny thing here is t hat i have the assosiation:
AcademyModule.belongsTo(Requirements, {foreignKey: 'requirements_id'});
Requirements.hasOne(AcademyModule, {foreignKey: 'requirements_id'});
First of all, I cannot answer why you are having problems accessing academyModule - it should be available via simple javascript scoping.
There are some unexplained bits of your code... You are pushing a single promise to associationPromises - Why not just return that promise?
You are accessing this.dataValues to create an AcademyModule instance - this will normally be undefined inside a promise handler.
Your issue could be solved more elegantly with nested creation, which is in 2.0.5: https://github.com/sequelize/sequelize/pull/3386
First of all you need to create the reverse association from Requirements --> AcademyModule
Requirements.hasOne(AcademyModule, {foreignKey: 'requirements_id'});
This is needed because the Requirement is the 'main model', which is used to create the AcademyModule
requirements.academy_module = academy_module;
Requirements.create(requirements, {
include: [AcademyModule]
});
By setting academy_module on the requirements object, and adding include as the 2nd parameter (similar to how you use includes for find) both are created and associated in one go.
You'll need to modify your models slightly. With your current model definition the table will contain an id column, but sequelzie does not know that it is the primary key. You either need to remove id completely from the Requirements model (in which case sequelize will add it automatically and mark it as primary key), or add primaryKey to it:
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true // optional - only if you actually want AI :)
}

Make ember to resolve hasMany relationship when loading

I'm currently facing a big problems for days. I'm using ember simple-auth plugin which provide me a session object accessible through the code or the templates. That session object store the account information such as username, id and rights.
My models are like this :
App.Right = DS.Model.extend({
label: DS.attr('string', { defaultValue: undefined })
});
App.Right.FIXTURES = [
{
id: 1,
label: 'Admin'
}, {
id: 2,
label: 'Manager'
}, {
id: 3,
label: 'User'
}
];
App.User = DS.Model.extend({
username: DS.attr('string'),
rights: DS.hasMany('right', {async: true})
});
App.User.FIXTURES = [
{
id: 1,
username: "Someone",
rights: [1]
}
];
Then I have (as specified on the simple-auth documentation) this setup :
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.Session.reopen({
account: function() {
var userId = this.get('userId');
if (!Ember.isEmpty(userId)) {
return container.lookup('store:main').find('user', userId);
}
}.property('userId')
});
...
}
});
Inside one of my view I'm doing this:
this.get('context.session.account.rights').toArray()
but it gives me an empty array. That piece of code is executed inside an Ember.computed property.
The question is how can I resolve the childrens of account before rendering the view ?
Since async: true this.get('context.session.account.rights') will return a promise object so you will have to use this.get('context.session.account.rights').then(... see: http://emberjs.com/api/classes/Ember.RSVP.Promise.html#method_then
Okay so I finally got it to work. It doesn't solve the original question because the original question was completely stupid. It's just IMPOSSIBLE to resolve relationships synchronously when you use the async: true. Trying to resolve it in advance is NOT the solution because you will still not know when it has actually resolved.
So here is the solution:
$.each(this.get('cellContent.buttonList'), function(i, button) {
button.set('hasAccess', false);
this.get('context.session.account').then(function(res) {
res.get('rights').then(function(result) {
button.set('hasAccess', Utils.hasAccess(result.toArray(), button.rights));
});
});
});
Using the following cellContent.buttonList definition:
buttonList: [
Ember.Object.create({
route: 'order',
label: 'Consult',
rights: 'all'
}), Ember.Object.create({
route: 'order.edit',
label: 'Edit',
rights: [1, 2]
})
]
Explanation
We have to use Ember.Object in order to have access to the set method. Using an Ember object is very handy. It allows us to change the value of properties after the render process making the view to update according to the new value you just set.
Because it updates the view, you don't have to care anymore whether your model has resolved or not.
I hope this will help people as much as it helps me.

Categories

Resources