2 dimensional table in Ember.js - ember.js

We're evaluating Ember.js (against Angular) for a complex app by building a few "toy apps". One of them is to present data in a table.
I've been through countless SO postings, the Ember Website, and other sites but can't quite find the key to making it work. The closest examples were at xeqtit and this fiddle.
Quite stuck.
Any pointers to how to set this up? Been reading web postings for days just don't see the answer out there...
The Problem Statement
To simplify the problem: imagine a list of routers, each router can have a variable number of interfaces, those interfaces have an address, status, etc.
The final table would look like:
__________________________________________________
Machine | Interfaces
rt1.rp.ps.com | UP | UP | UP | NG | UP | UP | NG |
rt2.rp.ps.com | UP | UP | |
rt3.rp.ps.com | UP | UP | UP | |
rt4.rp.ps.com | UP | UP | UP | NG | UP | UP | NG |
rt5.rp.ps.com | UP | UP | UP | NG | |
--------------------------------------------------
Note the variable number of columns.
The Objects:
App.Machine = Ember.Object.extend(
{
nickname: '',
address: '',
ifaces: []
});
App.Interface = Ember.Object.extend(
{
num: '',
status: '',
address: ''
});
The Markup
<script type="text/x-handlebars" data-template-name="machinelist">
<p>List of Machines</p>
<table>
{{#each App.MachinelistController }}
<tr>
<td>{{nickname}}</td>
<td>{{address}}</td>
<td>
<table>
{{#each p in App.MachinelistController.getInterfaces}}
<tr><td>{{p}}</td></tr>
{{/each}}
</table>
</td>
</tr>
{{/each}}
</table>
</script>
The Controller
The Controller first reads a database to get a list of machines and their addresses. It then queries each machine to fetch the list of interfaces. [I've simplified the code to show the core of the issue ... excuse any typos]
App.MachinelistController = Ember.ArrayController.create(
{
content: [],
getInterfaces: function(x, y, z)
{
// This didn't work
return this.getPath('interfaces.status');
}.property('#each.read'),
polling: false,
machinePollTime: 5000,
detailPollTime: 3000,
The list of machines is retrieved from a database via PHP. It populates the 'content' of the Controller with Machine objects, but no details on the interfaces are filled in yet:
fetch: function()
{
console.log('machine fetch');
var self = this;
$.get("be/getDVStorList.php", function(data, textStatus)
{
self.set('content', []);
var statusReport = jQuery.parseJSON(data);
statusReport.machineList.forEach(function(v)
{
var machine = App.Machine.create(
{
nickname: v['machineName'],
address: v['machineIpAddr']
});
self.pushObject( machine );
})
});
if (self.polling)
setTimeout( function() { self.fetch(); }, self.machinePollTime);
return this;
},
In a separate polling loop (still in the ArrayController), each machine in the content list is polled to get the info about its interfaces:
fetchDetails: function ()
{
console.log("fetch details");
var self = this;
self.forEach(function(item, index, self)
{
console.log(item.get('address'));
var addr = item.get('address');
var base = "http://"+ addr;
var slug = "/cgi-bin/DvStorGetStatus.cgi?Recording=1&Playback=1&Structured=1";
$.ajax(
{
url: base+slug,
timeout: 10000,
cache: false,
success: buildMachineCallback(addr),
});
});
if (self.polling)
setTimeout( function () { self.fetchDetails(); }, self.detailPollTime);
return true;
function buildMachineCallback(addr)
{
return function(data, textStatus, jqXHR) { updateDetailsCallback(data, textStatus, jqXHR, addr); };
};
This function is called when the poll to each machine returns. It adds the 'interfaces' into the data structure:
// Update *data structure* based on return values in XML
function updateDetailsCallback(data, textStatus, jqXHR, addr)
{
// might be more than one with this address
var theMachines = self.filterProperty('address')
var interfaceList = $(data).find('Interface');
var interfaces = [];
$(playInterfaceerList).each(function()
{
var anInterface = App.Interface.create();
var num = $(this).find('InterfaceNum').text();
anInterface.set('num', num);
anInterface.set('status', $(this).find('InterfaceStatus').text());
interfaces[num-1] = anInterface;
})
// update all machines sharing this IP address
theMachines.forEach(function (m, idx, tm)
{
tm[idx].set('alias', $(data).find('Generic').find('Alias').text());
tm[idx].set('health', $(data).find('Generic').find('SystemHealth').text());
interfaces.forEach(function(p)
{
tm[idx].get('ifaces').pushObject( App.Interface.create(p) );
})
});
}
}
});

There are two solutions that should works.
Ember-table, an Ember plugin: http://addepar.github.io/#/ember-table/overview
jQuery-datatable, a jQuery plugin: https://datatables.net/
Using the jQuery plugin will be more complicated because it is not directly linked to the ember render process.

Related

Kendo Multi-select in cascading scenario unable to populate initial values

I'm using Telerik for MVC and trying to get the multi-select to populate with the initial values in an Edit scenario.
<script>
function filterProducts() {
return {
manufacturerId: $("#ServiceBulletinItem_ManufacturerId").val()
};
}
function onManufacturerChange(e) {
var v = e.sender.dataItem().Value;
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: v }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
function InitialPopulate(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
$(document).ready(function () {
$('.control-datepicker').Zebra_DatePicker();
var m = $("#ServiceBulletinItem_ManufacturerId").val();
InitialPopulate(m);
});
</script>
<div class="form-group">
#Html.LabelFor(m => m.ManufacturerList, "Manufacturer", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().DropDownListFor(m => m.ServiceBulletinItem.ManufacturerId)
.HtmlAttributes(new { #class = "col-md-6 form-control" })
.Filter("contains")
.DataValueField("Value")
.DataTextField("Text")
.BindTo((IEnumerable<SelectListItem>)Model.ManufacturerSelectList)
.HtmlAttributes(new { style = "width:70%;" }).Events(e =>
{
e.Change("onManufacturerChange");
})
)
</div >
</div >
<div class="form-group">
#Html.LabelFor(m => m.ProductList, "Product", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().MultiSelectFor(m => m.ServiceBulletinItem.ApplicableProducts)
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products...")
)
</div>
</div>
I'm trying to populate the manufacturer drop down and the Product multiSelect. The ApplicableProducts item is an IEnumerable representing the ProductId's of all those previously selected and I know that when I select the manufacturer and it calls the GetCascadeProducts controller method it will return back a collection of ProductId and ProductName for all the manufacturers products of which those productId is the ApplicableProducts property should exist.
On document.ready I can call the InitialPopulate method with the manufacturerID which will populate the multiSelect items but can't seem to populate the initial values.
I couldnt get the binding working correctly so ended up using
#(Html.Kendo().MultiSelect()
.Name("ServiceBulletinItem_ApplicableProducts")
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products 2...")
.AutoBind(false)
)
and then on the using the following code on document ready to make an ajax call to populate the manufacturer and product controls
function PopulateProductsInitial(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result);
var s = $("#ServiceBulletinItem_Id").val();
$.post("#Url.Action("GetSBProducts", "ServiceBulletins")", { Id: s}, function (result) {
var arr = [];
result.forEach(function (element) {
arr.push(element.ProductId);
});
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.value(arr);
});
});
}
}
$(document).ready(function () {
//Populate Initial Values
PopulateProductsInitial($("#ServiceBulletinItem_ManufacturerId").val());
$('#YourButton').click(SendForm);
});
The problem then became sending the selected items back to the controller when the edit was complete which again seemed convoluted because the control was not bound and therefore I had to make an Ajax call to submit the data.
function SendForm() {
var items = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect").value();
//Manipulate into ServiceBulletinViewModel for the save
var data = {
Id: $("#ServiceBulletinItem_Id").val(),
ServiceBulletinItem: {
Id: $("#ServiceBulletinItem_Id").val(),
ManufacturerId: $("#ServiceBulletinItem_ManufacturerId").val(),
IssueDate: $('#ServiceBulletinItem_IssueDate').val(),
Heading: $('#ServiceBulletinItem_Heading').val(),
Details: $('#ServiceBulletinItem_Details').val(),
Url: $('#ServiceBulletinItem_Url').val(),
SelectedProducts: items
}
}
$.ajax({
type: 'POST',
url: '/ServiceBulletins/Edit',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
//Your success code here..
if (result.redirectUrl != null) {
window.location = result.redirectUrl;
}
},
error: function (jqXHR) {
if (jqXHR.status === 200) {
alert("Value Not found");
}
}
});
}
It all seemed a lot more convoluted than any of the demo's that teleriks and I couldnt find any good examples of binding from remote sources which looked similar.
As the binding is convention based I'm wondering if its possible to simplify the ajax calling for the post functionality with the correct naming of the controls so that I can simply get the selected items on the multiselect control or if the ajax post is the way to go.

Ember editable recursive nested components

I'm currently trying to build a component that will accept a model like this
"values": {
"value1": 234,
"valueOptions": {
"subOption1": 123,
"subOption2": 133,
"subOption3": 7432,
"valueOptions2": {
"subSubOption4": 821
}
}
}
with each object recursively creating a new component. So far I've created this branch and node components and its fine at receiving the data and displaying it but the problem I'm having is how I can edit and save the data. Each component has a different data set as it is passed down its own child object.
Js twiddle here : https://ember-twiddle.com/b7f8fa6b4c4336d40982
tree-branch component template:
{{#each children as |child|}}
{{child.name}}
{{tree-node node=child.value}}
{{/each}}
{{#each items as |item|}}
<li>{{input value=item.key}} : {{input value=item.value}} <button {{action 'save' item}}>Save</button></li>
{{/each}}
tree-branch component controller:
export default Ember.Component.extend({
tagName: 'li',
classNames: ['branch'],
items: function() {
var node = this.get('node')
var keys = Object.keys(node);
return keys.filter(function(key) {
return node[key].constructor !== Object
}).map(function(key){
return { key: key, value: node[key]};
})
}.property('node'),
children : function() {
var node = this.get('node');
var children = [];
var keys = Object.keys(node);
var branchObjectKeys = keys.filter(function(key) {
return node[key].constructor === Object
})
branchObjectKeys.forEach(function(keys) {
children.push(keys)
})
children = children.map(function(key) {
return {name:key, value: node[key]}
})
return children
}.property('node'),
actions: {
save: function(item) {
console.log(item.key, item.value);
}
}
});
tree-node component:
{{tree-branch node=node}}
Anyone who has any ideas of how I can get this working would be a major help, thanks!
Use:
save(item) {
let node = this.get('node');
if (!node || !node.hasOwnProperty(item.key)) {
return;
}
Ember.set(node, item.key, item.value);
}
See working demo.
I think this would be the perfect place to use the action helper:
In your controller define the action:
//controller
actions: {
save: function() {
this.get('tree').save();
}
}
and then pass it into your component:
{{tree-branch node=tree save=(action 'save')}}
You then pass this same action down into {{tree-branch}} and {{tree-node}} and trigger it like this:
this.attrs.save();
You can read more about actions in 2.0 here and here.

Route not seeing URL slug

I have an app that allows management of orders. The default view is a split view with orders on the left and selected order details on the right like so:
/Orders /Orders/:order_id
|-----------| |-------------------------------------|
| | | |
| | | |
| | | |
| | | |
| | | |
| List of | | Selected Item |
| Items | | Details |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
|-----------| |-------------------------------------|
I'd like to be able to edit an order in "full-screen" mode so that the URL and template looks like this:
/Orders/:order_id/edit/
|---------------------------------------------------|
| |
| |
| |
| |
| |
| |
| Order Edit Interface |
| |
| |
| |
| |
| |
| |
| |
|---------------------------------------------------|
My routes are currently setup as follows:
this.resource('Orders.edit', { path: 'Orders/:order_id/edit' } , function () {
this.route('customer-details');
this.route('vendor-details');
this.route('shipping-details');
}
this.resource('Orders', { path: 'Orders' }, function () {
this.resource('Order', { path: ':order_id' }, function () {
this.route('customer-details');
this.route('vendor-details');
this.route('shipping-details');
}
}
And my order routes look like this:
// Orders Route
App.OrdersRoute = Em.Route.extend({
model: function() {
return this.store.find('order');
},
afterModel: function (orders) {
this.transitionTo('orders.order', orders.get('firstObject') );
}
});
// Order Detail
App.OrdersOrderRoute = Em.Route.extend({
model: function(params) {
return this.store.find('order', params.order);
},
setupController: function (controller, model) {
controller.set('content', model);
}
});
// Order Edit Route
App.OrdersEditRoute = Em.Route.extend({
model: function(params) {
if (typeof params.order_id !== 'undefined') {
this.store.find('order', params.order_id).then(function (order) {
this.controllerFor('orders.edit').set('content', order);
});
} else if (typeof params.order !== 'undefined') {
this.store.find('order', params.order).then(function (order) {
this.controllerFor('orders.edit').set('content', order);
});
}
},
afterModel: function(eo) {
this.transitionTo('orders.edit.customer-details', order);
}
});
// Order Edit - Customer Details Route
App.OrdersEditCustomerDetailsRoute = Em.Route.extend({
model: function() {
try {
var order = this.get('order');
return order;
} catch (e) {
console.log('ERROR: ' + e);
}
},
beforeModel: function() {
this.set('order', this.modelFor('orders.edit'));
},
});
This setup works if I'm in the Orders/:order_id route/template and click the edit button which then sends me to Orders/:order_id/edit with the desired interface and data loaded. But if I try to refresh Orders/:order_id/edit in the browser window nothing loads and I get the following errors. I also don't hit any breakpoints set inside of the Orders/:order_id/edit route when accessing the URL this way.
Uncaught Error: Assertion Failed: Cannot delegate set('classification', N/A) to the 'content' property of object proxy <Synapse.EngineeringOrdersEditDetailsController:ember1242>: its 'content' is undefined. libs.js:2870
Ember.assert libs.js:2870
EmberObject.extend.setUnknownProperty libs.js:23933
set libs.js:9229
setPath libs.js:9289
set libs.js:9209
trySet libs.js:9306
(anonymous function) libs.js:3416
tryable libs.js:5964
tryFinally libs.js:10524
suspendListener libs.js:5967
_suspendObserver libs.js:8311
Binding._sync libs.js:3415
DeferredActionQueues.invoke libs.js:11346
DeferredActionQueues.flush libs.js:11398
Backburner.end libs.js:10861
Backburner.run libs.js:10916
apply libs.js:10745
run libs.js:9378
runInitialize libs.js:45596
n.Callbacks.j libs.js:2
n.Callbacks.k.fireWith libs.js:2
n.extend.ready libs.js:2
I libs.js:2
TypeError: undefined is not a function
at http://localhost:1337/js/app.js:27936:22
at invokeCallback (http://localhost:1337/js/libs.js:13310:19)
at publish (http://localhost:1337/js/libs.js:12980:9)
at publishFulfillment (http://localhost:1337/js/libs.js:13400:7)
at http://localhost:1337/js/libs.js:18818:9
at DeferredActionQueues.invoke (http://localhost:1337/js/libs.js:11348:18)
at Object.DeferredActionQueues.flush (http://localhost:1337/js/libs.js:11398:15)
at Object.Backburner.end (http://localhost:1337/js/libs.js:10861:27)
at Object.Backburner.run (http://localhost:1337/js/libs.js:10916:20)
at executeTimers (http://localhost:1337/js/libs.js:11241:12) libs.js:6663
logToConsole libs.js:6663
RSVP.onerrorDefault libs.js:49435
__exports__.default.trigger libs.js:12274
Promise._onerror libs.js:12998
publishRejection libs.js:13405
(anonymous function) libs.js:18818
DeferredActionQueues.invoke libs.js:11348
DeferredActionQueues.flush libs.js:11398
Backburner.end libs.js:10861
Backburner.run libs.js:10916
executeTimers libs.js:11241
(anonymous function) libs.js:11231
Uncaught Error: Assertion Failed: TypeError: undefined is not a function libs.js:2870
Ember.assert libs.js:2870
RSVP.onerrorDefault libs.js:49436
__exports__.default.trigger libs.js:12274
Promise._onerror libs.js:12998
publishRejection libs.js:13405
(anonymous function) libs.js:18818
DeferredActionQueues.invoke libs.js:11348
DeferredActionQueues.flush libs.js:11398
Backburner.end libs.js:10861
Backburner.run libs.js:10916
executeTimers libs.js:11241
(anonymous function)
I suspect it has something to do with having the Orders/order/edit route outside the hierarchy of the Orders resource but I was unable to get the outlets to play nicely to render the desired interface.
TLDR - How do I get the Orders/:order_id/edit to load the model properly from the URL slug? Using Ember 1.6.1 and Ember-data Fixture Adapter
Two approaches to achieve what you describe are,
Separate resource for editing (basically along the lines if what you've tried)
Nested resource and maintain a property based on which the template will only render the outlet part or not.
Example of these two approaches,
http://emberjs.jsbin.com/wupiwoculoxi/1/edit
hbs
<script type="text/x-handlebars" id="orders">
{{#unless showOnlyEditDetail}}
orders
<br/>
{{#each order in model}}
{{#link-to "order" order.id}}
{{order.name}}
{{/link-to}}
<br/> <br/>
{{/each}}
{{/unless}}
{{!--the following lines before the outlet are shown on purpose for demonstration reasons, the unless helper in this example can hide anything on this template--}}
<hr/>
<i>the value of showOnlyEditDetail:</i><b>{{showOnlyEditDetail}}</b>
<hr/>
{{outlet}}
</script>
<script type="text/x-handlebars" id="order">
the order
<br/>
<br/>
{{this.id}}
<br/>
<br/>
{{this.name}}
<br/>
<br/>
{{#link-to "orderEdit" this.id }}edit1{{/link-to}}
<i>(separate resource)</i>
<br/>
<br/>
{{#link-to "orderEdit2" this.id }}edit2{{/link-to}}
<i>(nested resource and maintaining property)</i>
</script>
<script type="text/x-handlebars" id="orderEdit">
edit the order
<br/>
{{this.id}}
<br/>
{{this.name}}
<br/>
{{#link-to "order" this.id}}back to the order{{/link-to}}
</script>
<script type="text/x-handlebars" id="orderEdit2">
edit2 the order
<br/>
{{this.id}}
<br/>
{{this.name}}
<br/>
{{#link-to "order" this.id}}back to the order{{/link-to}}
</script>
js
App = Ember.Application.create();
App.Router.map(function() {
this.resource("orderEdit",{path:"orders/:order_id/edit"}, function(){
});
this.resource('orders', { path: 'orders' }, function () {
this.resource('order', { path: ':order_id' }, function () {
this.route('customer-details');
this.route('vendor-details');
this.route('shipping-details');
});
this.resource("orderEdit2",{path:":order_id/edit2"}, function(){});
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo("orders");
}
});
var ordersData=[
{id:1,name:"order1"},
{id:2,name:"order2"},
{id:3,name:"order3"}
];
App.OrdersRoute = Ember.Route.extend({
model: function() {
return ordersData;
},
setupController:function(c,m){
c.set("showOnlyEditDetail",false);
this._super(c,m);
}
});
/*if the second approach is used then the controller with the specific property
(i.e. showOnlyEditDetail) must be defined.*/
App.OrdersController=Em.ArrayController.extend({
showOnlyEditDetail:false
});
App.OrderRoute = Ember.Route.extend({
model: function(params) {
return ordersData.findBy("id",parseInt(params.order_id,10));
}
});
App.OrderEditRoute = Ember.Route.extend({
model: function(params) {
return ordersData.findBy("id",parseInt(params.order_id,10));
}
});
App.OrderEdit2Route = Ember.Route.extend({
model: function(params) {
return ordersData.findBy("id",parseInt(params.order_id,10));
},
setupController:function(c,m){
this._super(c,m);
this.controllerFor("orders").set("showOnlyEditDetail",true);
},
actions:{
willTransition:function(){
this.controllerFor("orders").set("showOnlyEditDetail",false);
}
}
});

Bindings for nested component not working in ember-qunit

We have an ember component (let's call it component B), and the template for that component contains another component (component A). If we have computed properties in component B bound to properties in component A, the bindings are not working completely when we're testing using ember-qunit, but the bindings are working in the real application. In the tests, the bindings are working if we programmatically set values in components A or B, but if we use ember helpers (e.g. fillIn) to set component values, the bindings aren't getting fired. We don't experience this problem with non-nested components.
A jsfiddle that demonstrates the problem is here: http://jsfiddle.net/8WLpx/4/
Please ignore that parent component below could have just been an extension of the nested component. This is just to demonstrate the issue.
Code below if you'd rather:
HTML/handlebars
<!-- URL input -->
<script type="text/x-handlebars" data-template-name="components/url-input">
<div {{ bind-attr class=":input-group showErrors:has-error:" }}>
{{input value=web_url class="form-control"}}
</div>
</script>
<!-- video URL input -->
<script type="text/x-handlebars" data-template-name="components/video-url-input">
{{url-input class=class value=view.value selectedScheme=view.selectedScheme web_url=view.web_url}}
</script>
Component Javascript
//=============================== url input component
App.UrlInputComponent = Ember.Component.extend({
selectedScheme: 'http://',
value: function(key, value, previousValue) {
// setter
if (arguments.length > 1) {
this.breakupURL(value);
}
// getter
return this.computedValue();
}.property('selectedScheme', 'web_url'),
computedValue: function() {
var value = undefined;
var web_url = this.get('web_url');
if (web_url !== null && web_url !== undefined) {
value = this.get('selectedScheme') + web_url;
}
return value;
},
breakupURL: function(value) {
if(typeof value === 'string') {
if(value.indexOf('http://') != -1 || value.indexOf('https://') != -1) {
var results = /^\s*(https?:\/\/)(\S*)\s*$/.exec(value);
this.set('selectedScheme', results[1]);
this.set('web_url', results[2]);
} else {
this.set('web_url', value.trim());
}
}
},
onWebURLChanged: function() {
// Parse web url in case it contains the scheme
this.breakupURL(this.get('web_url'));
}.observes('web_url'),
});
//=============================== video url input component
App.VideoUrlInputComponent = Ember.Component.extend({
value: "http://",
selectedScheme: 'http://',
web_url: "",
});
Test Code
emq.moduleForComponent('video-url-input','Video URL Component', {
needs: ['component:url-input',
'template:components/url-input'],
setup: function() {
Ember.run(function() {
this.component = this.subject();
this.append();
}.bind(this));
},
});
emq.test('Test fill in url programmatically', function() {
var expectedScheme = 'https://';
var expectedWebURL = 'www.someplace.com';
var expectedURL = expectedScheme + expectedWebURL;
Ember.run(function() {
this.component.set('selectedScheme', expectedScheme);
this.component.set('web_url', expectedWebURL);
}.bind(this));
equal(this.component.get('value'), expectedURL, "URL did not match expected");
});
emq.test('Test fill in url via UI', function() {
var expectedURL = 'https://www.someplace.com';
fillIn('input', expectedURL);
andThen(function() {
equal(this.component.get('value'), expectedURL, "URL did not match expected");
}.bind(this));
});
The this.append() cannot happen in the test setup; it must happen in the "test" method because the ember qunit "test" wrapper clears all of the views before calling the standard qunit "test" method.

Display multiples sums in an table with emberjs/handlebars

I'm working on a personnal project based on ember and I'm a bit stuck with something I'd like to implement :
I need to be able to display a table (based on the model of an ArrayController) and inside this table I need to display a column summing all values from another column for all the previous rows.
I know how to sum all rows into only one value, but I don't know how to do that for every rows.
Here is what I need to achieve :
__________________________
value | sum
__________________________
1 | 1
__________________________
2 | 3
__________________________
-1 | 2
and so on...
"value" is a field for each DS.Model in the model of my ArrayController.
I'm not looking for the final implementation but some hints on how to achieve this.
Thanks for reading me,
Pierre.
How about something like this:
(javascript)
App = Ember.Application.create();
App.Router.map(function() {
});
App.IndexController = Ember.Controller.extend({
actions: {
inc: function () {
this.get("model")[1].incrementProperty("val");
}
}
});
App.IndexRoute = Ember.Route.extend({
model: function() {
var a = App.Model.create({
val: 1
});
var b = App.Model.create({
val: 2,
prev: a
});
var c = App.Model.create({
val: -1,
prev: b
});
return [a, b, c];
}
});
App.Model = Ember.Object.extend({
val: 0,
prev: null,
sum: function () {
var val = this.get("val"),
prev = this.get("prev");
if (!prev) {
return val;
}
return prev.get("sum") + val;
}.property("val", "prev.sum")
});
(template)
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li>{{item.val}} | {{item.sum}}</li>
{{/each}}
</ul>
<button type="button" {{action inc}}>Inc</button>
</script>
Working example here
The only caveat, you have to create and maintain the links between the elements. If you reshuffle the array, add new elements etc... you have to recreate all the references manually.
Update:
I'm intrigued by this problem, so I gave it another go.
App.IndexRoute = Ember.Route.extend({
model: function() {
var col = [];
col.push(App.Model.create({
val: 1,
all: col
}));
col.push(App.Model.create({
val: 2,
all: col
}));
col.push(App.Model.create({
val: -1,
all: col
}));
return col;
}
});
App.Model = Ember.Object.extend({
val: 0,
all: null,
prev: function () {
var all = this.get("all");
for (var i = 0; i < all.length - 1; i++) {
if (all[i + 1] === this) {
return all[i];
}
}
return null;
}.property("all.[]"),
sum: function () {
var val = this.get("val"),
prev = this.get("prev");
if (!prev) {
return val;
}
return prev.get("sum") + val;
}.property("val", "prev.sum")
});
Updated live demo is here.
This will track changes in parent collection and automatically determine the previous element.