I have three stores in my application EmployeesTree, Empolyees, History.
First one is source for treegrid, second for combobox that is used to pass additional parameters while loading third stode.
My first approach was to create second store using standard proxy and load employees from server using request.
In my history grid I have:
me.c = Ext.create("MyApp.Store.Employees");
me.c.load();
me.tbar=
[
{
xtype: 'combo',
fieldLabel: 'Employee',
width: 300,
labelWidth: 65,
typeAhead: false,
forceSelection: true,
store: me.c,
queryMode: 'local',
displayField: 'Name',
valueField: 'Id',
listeners: {
select: function (combo, records) {
console.log(records[0].get('Id'));
me.store.proxy.extraParams.uid = records[0].get('Id');
me.store.load();
},
scope: this
}
}
];
But this cause additional request that I would like to avoid.
My second idea was to create empty store and add every leaf from first store to it (because it will contains the same list of employees but without hierarchy)
I've created method that should copy all leafs to new store:
cloneStore: function(source) {
var target = Ext.create('Sch.data.ResourceStore', {
model: 'MyApp.Model.Employee'
});
var node = source.getRootNode();
node.eachChild(function (myNode) {
if (myNode.isLeaf()) {
var newData = Ext.clone(myNode.copy().data);
var model = new source.model(newData, newData.id);
target.add(model);
}
});
Right now this copies first level of my store, I need to add recursive to this method so it will add leafs from subnodes.
Also when I do var newData = Ext.clone(myNode.copy().data); my new record will have all fields of that treestore, how can I copy only two fields from my source store to target. I need only 2 fields (id, Name).
How should I modify cloneStore method to recursively copy employees from treeStore to Store and how can I copy only needed fields? Do I must specify them in my method.
EDIT
I've modified my function so it can be called recursively.
cloneStore: function(source) {
var target = Ext.create('MyApp.Store.EmployeesForComboBox');
var node = source.getRootNode();
this._addLeafsToStore(target, node);
return target;
},
_addLeafsToStore: function(store, node) {
node.eachChild(function (myNode) {
if (myNode.isLeaf()) {
store.add({
Id: myNode.get('Id'),
Name: myNode.get('Name')
});
} else {
this._addLeafsToStore(store, myNode);
}
},this);
},
This works quite well, but I would like to create more universal function, because right now I have hardcoded Store and fields I want to copy.
Additional question:
What option is better: to add new record to store using Ext.clone (first solution) or adding only necessary fields (second solution)?
To create a more generic function, you'll need to change hardcoded stuff as function parameters:
cloneStore: function(source, target, fields) {
var node = source.getRootNode();
this._addLeafsToStore(target, node, fields);
return target;
},
_addLeafsToStore: function(store, node, fields) {
node.eachChild(function (myNode) {
if (myNode.isLeaf()) {
var config = {};
for (var i = 0; i < fields.length; i++) {
config[fields[i]] = myNode.get(fields[i]);
}
store.add(config);
} else {
this._addLeafsToStore(store, myNode, fields);
}
},this);
},
and call it like this:
var cloned = someObj.cloneStore(source, Ext.create('MyApp.Store.EmployeesForComboBox'), ['Id', 'Name']);
For the additional question, I think the second solution will perform better if your data set is large.
Related
I have a form with many fields attached to a data - this.myData:
data: function() {
return {
isDataChanged: false,
myData: {},
myChangedData: {
default: '',
default1: {},
default2: []
},
}
},
myData is populated from a response from the server and it populates the form values.
myChangedData is for the new values, which are changed v-on:input="onChangeMyData($event, 'default')":
onChangeMyData(e, name, required = false){
const val = e.target.value.trim();
this.myChangedData[name] = val;
console.log(this.myChangedData)
this.checkIsmyDataChanged();
},
I can use the same method, providing a key as a second param. With the method checkIsmyDataChanged I am checking is it changed some field in the form. This method loops through myChangedData and compares its properties with changedData and if there is a difference this.isDataChanged = true.
The problem is that, I have a complicated structure of mydata/mydatachanged. default1 has objects in it and default1 is an array of objects. This means that, I can't use onChangeMyData, but other methods with different checks (validations) and now I need to call in all of them this.checkIsmyDataChanged();.
I created a watch for myChangedData:
watch:{
myChangedData: {
handler: function (newVal) {
console.log('change')
},
deep: true
},
},
, but it doesn't execute on change data
Did you try with Vue.set ? Source
Change this.myChangedData[name] = val; to
this.$set(this.myChangedData, 'name', val)
Thanks to that, the modification on the object should be detected and execute the watcher.
I am using Mithril.JS and it looks like my vm is undefined where as prior it wasn't.
I searched around and there is very little out there in terms of mithril.js.
Code:
var app = {};
var apiData;
app.getData = function () {
m.request({
method: 'GET',
url: '/api/stocks',
}).then(function(data){
data = apiData;
})
};
app.App = function(data){ // model class
this.plotCfg = {
chart: {
renderTo: "plot"
},
rangeSelector: {
selected: 4
},
yAxis: {
labels: {
formatter: function () {
return (this.value > 0 ? ' + ' : '') + this.value + '%';
}
},
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
plotOptions: {
series: {
compare: 'percent',
showInNavigator: true
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
valueDecimals: 2,
split: true
},
series: [{
name: 'Kyle\'s Chart',
data: apiData
}]
};
};
app.controller = function() { // controller
this.apk = new app.App();
this.cfg = this.apk.plotCfg;
};
app.plotter = function(ctrl) { // config class
return function(elem,isin) {
if(!isin) {
m.startComputation();
var chart = Highcharts.StockChart(ctrl.cfg);
m.endComputation();
}
};
};
app.view = function(ctrl) { // view
return m("#plot[style=height:400px]", {config: app.plotter(ctrl)})
};
app.Stock = function(data) {
this.date_added = m.prop(new Date());
this.symbol = m.prop(data.symbol);
this.id = m.prop(data.id)
};
app.SymbolList = Array;
app.vm = (function() {
var vm = {}
vm.init = function() {
//a running list of todos
vm.list = new app.SymbolList();
//a slot to store the name of a new todo before it is created
app.parseData = function (data) {
for (var i =0; i< list.length ;i++) {
console.log(list[i].stock);
var stockSymbol = data[i].stock;
vm.list.push(new app.Stock({symbol : stockSymbol}));
}
app.parseData(apiData);
vm.symbol = m.prop("");
//adds a todo to the list, and clears the description field for user convenience
vm.add = function() {
var data = vm.symbol();
if (vm.symbol()) {
data = {'text': data.toUpperCase()};
m.request({method: 'POST',
url: '/api/stocks',
data: data,
}).then(function(list) {
vm.list = [];
for (var i =0; i< list.length ;i++) {
console.log(list[i].stock);
var stockSymbol = list[i].stock;
vm.list.push(new app.Stock({symbol : stockSymbol}));
}
return;
})
vm.symbol("");
}
};
}
return vm
}
}())
app.controller2 = function() {
app.vm.init();
}
app.view2 = function() {
return [
m('input', { onchange: m.withAttr('value', app.vm.symbol), value: app.vm.symbol()}),
m('button.btn.btn-active.btn-primary', {onclick: app.vm.add}, 'Add Stock'),
m('ul', [
app.vm.list.map(function(item , index) {
return m("li", [
m('p', item.symbol())
])
})
])
]
};
m.mount(document.getElementById('chart'), {controller: app.controller, view: app.view}); //mount chart
m.mount(document.getElementById('app'), {controller: app.controller2, view: app.view2}); //mount list
<div id="app"></div>
<div id="chart"></div>
<script src="https://cdn.rawgit.com/lhorie/mithril.js/v0.2.5/mithril.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
The error that pops up in chrome is this:
app.js:119 Uncaught TypeError: Cannot read property 'init' of undefined
at new app.controllerT (app.js:119)
at ea (mithril.js:1408)
at Function.k.mount.k.module (mithril.js:1462)
at app.js:135
It was fine before I added the second mount-point, view and controller.
Any ideas?
The problem is that app.vm doesn't expose init.
The current code for app.vm looks like this:
app.vm = (function(){
var vm = {}
vm.init = function(){
/* lots of stuff... */
return vm
}
}())
This means the internal vm.init returns vm, but the app.vm IIFE doesn't return anything. It should be:
app.vm = (function(){
var vm = {}
vm.init = function(){
/* lots of stuff... */
}
return vm
}())
It's very difficult to read your application structure because it's full of a variety of exotic patterns that don't seem to be useful. Admittedly, the 'vm' closure is a pattern introduced in Mithril guides, but I think it's far easier to write, reason about and debug applications if we avoid all these closures, initialisation calls, constructors, nested objects and namespaces.
The idea behind 'view models' comes from the state of web app development when Mithril was originally released (early 2014), when one of the principle concerns in front-end app development was a perceived lack of structure, and Mithril felt it necessary to show people how to structure objects. But structure of this form is only useful if it clarifies intent - in the code above it confuses things. For example, app.getData isn't called anywhere, and it assigns an empty global variable to its own argument before disposing of it. This kind of thing would be easier to reason about if we had less objects.
Here's the same code with some extra fixes and an alternate structure. The principles at work in this refactor:
We're no longer writing any of our own constructors or closures, resulting in less dynamic code execution and avoiding potential for errors like app.vm.init
We're no longer attaching things to objects unless that structure is useful or meaningful, and using simple variables or declaring things at the point of use if they're only used once, resulting in less references and less structural complexity
We use object literals - var x = { y : 'z' } instead of var x = {}; x.y = 'z' so we can see holistic structures rather than having to mentally interpret code execution to work out how objects will be built at runtime
Instead of using one big generic app.vm to store everything, we separate our app model into the places where they are relevant and use functions to pass values from one place to another, allowing us to split our complexity. I'll elaborate on this after showing the code:
// Model data
var seriesData = []
// Model functions
function addToSeries(data){
seriesData.push.apply(seriesData,data)
}
function getData( symbol ){
m.request( {method: 'POST',
url: '/api/stocks',
data: { text : symbol.toUpperCase() },
} ).then(function(list) {
return list.map(function( item ){
return makeStock( { symbol : item.stock } )
} )
} )
}
function makeStock( data ) {
return {
date_added : new Date(),
symbol : data.symbol,
id : data.id
}
}
// View data
var chartConfig = {
rangeSelector: {
selected: 4
},
yAxis: {
labels: {
formatter: function () {
return (this.value > 0 ? ' + ' : '') + this.value + '%';
}
},
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
plotOptions: {
series: {
compare: 'percent',
showInNavigator: true
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
valueDecimals: 2,
split: true
},
series: [{
name: 'Kyle\'s Chart',
data: seriesData
}]
}
// Components
var chartComponent = {
view : function(ctrl) {
return m("#plot[style=height:400px]", {
config: function(elem,isin) {
if(!isin)
Highcharts.StockChart(elem, chartConfig)
}
})
}
}
var todosComponent = {
controller : function(){
return {
symbol : m.prop('')
}
},
view : function( ctrl ){
return [
m('input', {
onchange: m.withAttr('value', ctrl.symbol),
value: ctrl.symbol()
}),
m('button.btn.btn-active.btn-primary', {
onclick: function(){
if( ctrl.symbol() )
getData( ctrl.symbol() )
.then( function( data ){
addToSeries( data )
} )
ctrl.symbol('')
}
}, 'Add Stock'),
m('ul',
todos.map(function(item) {
return m("li",
m('p', item.symbol)
)
})
)
]
}
}
// UI initialisation
m.mount(document.getElementById('chart'), chartComponent)
m.mount(document.getElementById('app'), todosComponent)
There is no more app, or vm, or list. These end up being unhelpful because they're so vague and generic they get used to store everything - and when one object contains everything, you may as well have those things freely available.
The core dynamic data list is now called seriesData. It's just an array. In order to interact with it, we have 3 simple functions for mutating the series data, fetching new data, and creating a new data point from input. There's no need for constructors here, and no need for props - props are a Mithril utility for being able to conveniently read and write data from an input - they're incompatible with the Highcharts API in any case.
That's all the model data we need. Next we have the code specific to our UI. The Highcharts config object references seriesData, but apart from that its an esoteric object written to conform with Highcharts API. We leave out renderTo, because that's determined dynamically by our Mithril UI.
Next comes the components, which we write as object literals instead of piecing them together later - a component controller only makes sense in relation to its view. The chartComponent doesn't actually need a controller, since it has no state and just reads previously defined model and view data. We supply the element reference directly to the Highcharts API in the config function. Because this function is only used once in a single place, we declare it inline instead of defining it in one place and binding it somewhere else. start/endComputation are unnecessary since the process is synchronous and there's no need to stop Mithril rendering during this process.
I couldn't quite work out how the 'todos' model was meant to work, but I assumed that the second component is designed to provide an alternate view of data points and allow user input to define and fetch more data. We store the 'symbol' prop in the controller here, since it's a stateful property that's used exclusively by the view. It's our only stateful property relating to this component, so that's all we define in the controller. Earlier on we simplified the model-related functions - now in the view we interact with these, passing in the symbol data explicitly instead of defining it elsewhere and retrieving it in another place. We also reset the value here, since that's an aspect of this component's logic, not the overal data model.
I'm fairly new to ember and I'd like to know whats the fastest way to extract the data out of ember objects. I've loaded my model with a very large amount of records using this.store.find('modelName);` in my route.
I created a component on my view using {{kendo-ui.kendo-table descriptor=tableDescriptor data=model}}. My controller defined other arguments to be passed to my component (descriptor).
In my components.js I'm' getting the data passed over by using
export default Ember.Component.extend({
didInsertElement: function() {
var columns = this.get('descriptor.columns'); // this is right
var model = this.get('data')['content']; // this returns the objects of the model
var height = this.get('descriptor.height'); // this is ok too
Ember.$('#kendo-table').kendoGrid({
dataSource: {
data: model,
pageSize: 100
},
height: height,
scrollable: {
virtual: true
},
groupable: true,
sortable: true,
columns: columns
});
}
});
On the line var model = this.get('data')['content'];, this gives me an Array of Ember Classes. Inside each class, there is a _data object that holds the value of the actual class.
The easiest solutions is to just loop through and extract the _data but that is no good for larger model array. Is there a quick way to extract all the _data from my array of ember objects?
You could use getProperties method. http://emberjs.com/api/classes/Ember.Object.html#method_getProperties
To get the values of multiple properties at once, call getProperties with a list of strings or an array:
record.getProperties('firstName', 'lastName', 'zipCode');
// { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
You could define computed property dataArray:
dataArray: function() {
return this.get('data').map( function(item) {
return item.getProperties('id', ... ); // your list of properties
});
}.property('data.[]'),
didInsertElement: function() {
//...
Ember.$('#kendo-table').kendoGrid({
dataSource: {
data: this.get('dataArray'),
//...
},
// ...
});
}
UPDATE:
for records (DS.Model) you could use toJSON method. Use DS.JSONSerializer to get the JSON representation of a record.
toJSON takes an optional hash as a parameter, currently supported options are:
includeId: true if the record's ID should be included in the JSON representation.
http://emberjs.com/api/data/classes/DS.Model.html#method_toJSON
I am trying to get a json data render a dojo tree.
You can see what I am doing at http://jsfiddle.net/F53Ge/38/
require(["dojo/parser","dojo/json","dojo/store/Memory","dijit/tree/ObjectStoreModel","dijit/Tree", "dojo/window"], function (parser,json,Memory,ObjectStoreModel,Tree,win) {
var data = [{"id":"root","team":[{"teamId":1,"teamname":"JMK_TEST_1","parentteamId":0,"associatedList":[{"type":"9117","number":"1011D1P"}]},{"teamId":174,"teamName":"JJG_PARENT_3","parentteamId":0,"associatedList":[{"type":"8205","number":"062072T"}]},{"teamId":172,"teamName":"JJG_PARENT_1","parentteamId":0,"subteamsList":[{"teamId":175,"teamName":"JJG_Subteam_1","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]},{"teamId":176,"teamName":"JJG_Subteam_2","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]}],"associatedList":[{"type":"7945","number":"KQZGTNC"}]},{"teamId":221,"teamName":"JJG_Parent_4","parentteamId":0,"subteamsList":[{"teamId":222,"teamName":"JJG_Subteam_4_1","parentteamId":221,"associatedList":[{"type":"9117","number":"10E7683"},{"type":"9119","number":"514DDB2"},{"type":"8233","number":"102FE9P"},{"type":"7978","number":"KDGYKLL"},{"type":"7978","number":"99A9880"}]}]},{"teamId":106,"teamName":"JMK_TEST","parentteamId":0,"subteamsList":[{"teamId":107,"teamName":"JMK_TEST1","parentteamId":106,"subteamsList":[{"teamId":173,"teamName":"JJG_PARENT_2","parentteamId":107,"subteamsList":[{"teamId":178,"teamName":"JJG_Subteam_2_1","parentteamId":173,"associatedList":[{"type":"9117","number":"10E7683"}]}],"associatedList":[{"type":"7945","number":"KQZGTNC"}]}]}]}]}];
var store = new Memory({
data: data,
getChildren: function(object){
return object.team || [];
}
});
var model = new ObjectStoreModel({
store: store,
query: { id:'root' },
mayHaveChildren: function (item) {
return "subteamsList" in item;
}
});
var tree = new Tree({
model: model,
showRoot: false,
autoExpand: true,
persist: false,
onClick: function (item, treeNode, e) {
selectednodeid = this.selectedNode;
win.scrollIntoView(selectednodeid);
alert(selectednodeid);
}
},"oopt_list");
tree.startup();
});
First I am not see children nodes and also do not know how to pass the label as it shows undefined for the list.
Any help is appreciated.
Also let me know if I should use the ForestModel instead.
Basically I am trying to show the json data in a tree hireachy and want to know which node the user clicked so I can do some action based on that.
Regards
BumbleBee
To define which property should be used as label, you need to define 'labelAttr' on model.
The reason, why you do not see the child nodes is, that you store is missing idProperty on Store.
(or you have different id property on root item 'id' and different on other items 'teamId').
Using ObjectStoreModel instead of ForestModel is correct. You are using Memory store, which is implementaion of new dojo/store API. (ForestModel should be used only with older dojo/data API)
See your sample updated here
http://jsfiddle.net/F53Ge/42/
require(["dojo/parser","dojo/json","dojo/store/Memory","dijit/tree/ObjectStoreModel","dijit/ Tree", "dojo/window"], function (parser,json,Memory,ObjectStoreModel,Tree,win) {
var data = [{"teamId":"root","subteamsList":[{"teamId":1,"teamName":"JMK_TEST_1","parentteamId":0,"associatedList":[{"type":"9117","number":"1011D1P"}]},{"teamId":174,"teamName":"JJG_PARENT_3","parentteamId":0,"associatedList":[{"type":"8205","number":"062072T"}]},{"teamId":172,"teamName":"JJG_PARENT_1","parentteamId":0,"subteamsList":[{"teamId":175,"teamName":"JJG_Subteam_1","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]},{"teamId":176,"teamName":"JJG_Subteam_2","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]}],"associatedList":[{"type":"7945","number":"KQZGTNC"}]},{"teamId":221,"teamName":"JJG_Parent_4","parentteamId":0,"subteamsList":[{"teamId":222,"teamName":"JJG_Subteam_4_1","parentteamId":221,"associatedList":[{"type":"9117","number":"10E7683"},{"type":"9119","number":"514DDB2"},{"type":"8233","number":"102FE9P"},{"type":"7978","number":"KDGYKLL"},{"type":"7978","number":"99A9880"}]}]},{"teamId":106,"teamName":"JMK_TEST","parentteamId":0,"subteamsList":[{"teamId":107,"teamName":"JMK_TEST1","parentteamId":106,"subteamsList":[{"teamId":173,"teamName":"JJG_PARENT_2","parentteamId":107,"subteamsList":[{"teamId":178,"teamName":"JJG_Subteam_2_1","parentteamId":173,"associatedList":[{"type":"9117","number":"10E7683"}]}],"associatedList": [{"type":"7945","number":"KQZGTNC"}]}]}]}]}];
var store = new Memory({
data: data,
idProperty:"teamId",
getChildren: function(object){
return object.subteamsList || [];
}
});
var model = new ObjectStoreModel({
store: store,
query: { teamId:'root' },
mayHaveChildren: function (item) {
console.log("may",item)
return "subteamsList" in item;
},labelAttr :"teamName"
});
var tree = new Tree({
model: model,
showRoot: false,
autoExpand: true,
persist: false,
onClick: function (item, treeNode, e) {
selectednodeid = this.selectedNode;
win.scrollIntoView(selectednodeid);
alert(selectednodeid);
}
},"oopt_list");
tree.startup();
});
I have 2 models and one collection. JobSummary is a model, JobSummaryList is a collection of JobSummary items, and then I have a JobSummarySnapshot model that contains a JobSummaryList:
JobSummary = Backbone.Model.extend({});
JobSummaryList = Backbone.Collection.extend({
model: JobSummary
});
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
}
});
When I call fetch on the JobSummarySnapshot object, it gets everything... Except when I move through the summaryList collection they are all of type object and not JobSummary.
I suppose this makes sense since other than the defaults object, it doesn't know that the summaryList should be of type JobSummaryList. I can go through each item and convert it to a JobSummary object, but I was hoping there was a way to do it without having to do it manually.
Here's my test code (working jsfiddle here):
var returnData = {
pageNumber: 3,
summaryList: [
{
id: 5,
name: 'name1'},
{
id: 6,
name: 'name2'}
]
};
var fakeserver = sinon.fakeServer.create();
fakeserver.respondWith('GET', '/JobSummaryList', [200,
{
'Content-Type': 'application/json'},
JSON.stringify(returnData)]);
var callback = sinon.spy();
var summarySnapshot = new JobSummarySnapshot();
summarySnapshot.bind('change', callback);
summarySnapshot.fetch();
fakeserver.respond();
var theReturnedList = callback.getCall(0).args[0].attributes.summaryList;
_.each(theReturnedList, function(item) {
console.log('Original Item: ');
console.log(item instanceof JobSummary); // IS FALSE
var convertedItem = new JobSummary(item);
console.log('converted item: ');
console.log(convertedItem instanceof JobSummary); // IS TRUE
});
UPDATE:
It occurred to me that I could override the parse function and set it that way... I have this now:
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
},
parse: function(response) {
this.set({pageNumber: response.pageNumber});
var summaryList = new JobSummaryList();
summaryList.add(response.summaryList);
this.set({summaryList: summaryList});
}
});
This works so far. Leaving the question open in case someone has comment on it....
Your parse() function shouldn't set() anything, its a better practice to just return the attributes, Backbone will take care of setting it. e.g.
parse: function(response) {
response.summaryList = new JobSummaryList(response.summaryList);
return response;
}
Whatever you return from parse() is passed to set().
Not returning anything (which is like returning undefined) is the same as calling set(undefined), which could cause it not to pass validation, or some other unexpected results if your custom validate()/set() methods expects to get an object. If your validation or set() method fails because of that, the options.success callback passed to Backbone.Model#fetch() won't be called.
Also, to make this more generic, so that set()ing to a plain object from other places (and not only from the server response) also effects it, you might want to override set() instead:
set: function(attributes, options) {
if (attributes.summaryList !== undefined && !(attributes.summaryList instanceof JobSummaryList)) {
attributes.summaryList = new JobSummaryList(attributes.summaryList);
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
You might also find Backbone-relational interesting - it makes it much easier to deal with collections/models nested inside models.
edit I forgot to return from the set() method, the code is now updated