I am developing a table editor with Ember.js. I created a view called FocusedTextField that focuses the text field when it is rendered. I want to implement a tabbing ability such that hitting tab will focus the next cell in a row. The current code will change the currently selected cell to be uneditable and change the next cell to be a text field, but will not focus on the next field's value. It seems that the code not working is an effect of timing. What's a better way of approaching this problem?
Here's my JSBin: http://emberjs.jsbin.com/tey/12/edit
jQuery 1.10.2
Handlebars 1.2.1
Ember 1.1.2
HTML:
<script type="text/x-handlebars" data-template-name="row">
<tr>
{{#collection cellCollection}}
{{view view.content.view}}
{{/collection}}
</tr>
</script>
<script type="text/x-handlebars" data-template-name="cell">
<td {{action "click"}}>
{{#if editMode}}
{{view textField valueBinding=value}}
{{else}}
{{value}}
{{/if}}
</td>
</script>
<script type="text/x-handlebars" data-template-name="table">
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Points</th>
</tr>
{{#collection rowCollection}}
{{view view.content.view}}
{{/collection}}
</table>
</script>
<div id="main"></div>
JavaScript:
App = Ember.Application.create();
var TemplatedViewController = Ember.Object.extend({
templateFunction: null,
viewArgs: null,
viewBaseClass: Ember.View,
view: function () {
var controller = this;
var viewArgs = this.get('viewArgs') || {};
var args = {
template: controller.get('templateFunction'),
controller: controller
};
args = $.extend(viewArgs, args);
return this.get('viewBaseClass').extend(args);
}.property('templateFunction', 'viewArgs'),
appendView: function (selector) {
this.get('view').create().appendTo(selector);
},
appendViewToBody: function () {
this.get('view').create().append();
},
appendPropertyViewToBody: function (property) {
this.get(property).create().append();
}
});
var FocusedTextField = Ember.TextField.extend({
focusTheTextField: function() {
this.$().focus();
}.on('didInsertElement')
});
var Cell = TemplatedViewController.extend({
row: null,
editMode: false,
value: null,
textField: function () {
var cell = this;
return FocusedTextField.extend({
keyDown: function (event) {
// Hitting the enter key disables edit mode
if (event.keyCode === 13) {
cell.set('editMode', false);
// Hitting the tab key selects the next cell
} else if (event.keyCode === 9) {
cell.set('editMode', false);
var nextCell = cell.getNextCell();
nextCell.set('editMode', true);
}
}
});
}.property(),
flipEditMode: function () {
if (this.get('editMode')) {
this.set('editMode', false);
} else {
this.set('editMode', true);
}
},
click: function () {
console.log('cell clicked, value: '+this.get('value'));
this.flipEditMode();
},
getNextCell: function () {
return this.get('row').getNextCell(this);
},
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'cell'
});
}.property()
});
var Row = TemplatedViewController.extend({
headers: ['firstName', 'lastName', 'points'],
firstName: null,
lastName: null,
points: null,
cells: null,
cellCollection: function () {
return Ember.CollectionView.extend({
content: this.get('cells')
});
}.property('cells'),
init: function () {
this._super();
var row = this;
var cells = [];
this.get('headers').forEach(function (item, index, enumerable) {
var header = item;
var value = row.get(header);
var cell = Cell.create({
row: row,
value: value
});
cells.pushObject(cell);
});
this.set('cells', cells);
},
getNextCell: function (cell) {
if (this.get('cells').contains(cell)) {
var lastIndex = this.get('cells').length - 1;
var cellIndex = this.get('cells').indexOf(cell);
if (cellIndex < lastIndex) {
var nextIndex = cellIndex + 1;
return this.get('cells')[nextIndex];
}
}
},
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'row'
});
}.property()
});
var rows = [];
var DATA = [
{first_name: 'Jill', last_name: 'Smith', points: 50},
{first_name: 'Eve', last_name: 'Jackson', points: 94},
{first_name: 'John', last_name: 'Doe', points: 80},
{first_name: 'Adam', last_name: 'Johnson', points: 67}
];
DATA.forEach(function (item, index, enumerable) {
var row = Row.create({
firstName: item.first_name,
lastName: item.last_name,
points: item.points
});
rows.pushObject(row);
});
var Table = TemplatedViewController.extend({
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'table'
});
}.property(),
rows: null,
rowCollection: function () {
return Ember.CollectionView.extend({
content: this.get('rows')
});
}.property('rows')
});
var table = Table.create({rows: rows});
$(function () {
table.appendView('#main');
});
Wrap the call to focus() in Ember.run.next like so:
var FocusedTextField = Ember.TextField.extend({
focusTheTextField: function() {
var self = this;
Ember.run.next( function() { self.$().focus(); });
}.on('didInsertElement')
});
For a description of Ember.run.next, see: http://emberjs.com/api/classes/Ember.run.html#method_next
A good description of the Ember run loop: What is Ember RunLoop and how does it work?
Related
For some reason, foreach in Knockout.js doesn't iterate through my observable array.
In my HTML I have this which works perfectly fine with the observable model:
<div class="field-group">
<label class="popup-label" for="email">Email</label>
<span class="email" data-bind="text: masterVM.employeeVM.Email"></span>
</div>
But in the same model, this code doesn't work:
<ul data-bind="foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.tagName"><i class="zmdi zmdi-delete"></i></span>
</li>
</ul>
There are two models:
Employee
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
self.errors = ko.validation.group(this, {
deep: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and Tag
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
self.errors = ko.validation.group(this, {
live: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and handler function:
var employeeHandler = function () {
var self = this;
self.getEmployeeDetails = function (header) {
$.ajax({
url: masterVM.controller.renderEmployeeDetails,
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({ id: header.data("employeeid") }),
success: function (result) {
masterVM.employeeVM = new observableEmployee(
result.model.Id,
result.model.Email,
result.model.Tags
);
ko.applyBindings(masterVM, $("#employee-planning-selected")[0]);
//header.parent().addClass('open');
//header.next().slideDown('normal');
//hideLoader(header);
console.log('get employee details');
$(document).on('click', "div.employee", onNameCardClick);
},
error: function (xhr, ajaxOptions, thrownError) {
alert('Error!');
}
});
}}
In my HTML file
<script>
masterVM = {
controller: {
renderEmployeeDetails: '#(Html.GetActionUrl<EmployeesController>(c => c.RenderEmployeeDetails(0)))'
},
employeeHandler: new employeeHandler(),
employeeVM: new observableEmployee(0, '', '', '', '')
}
ko.applyBindings(masterVM);
</script>
Tried something like this, and still nothing
<!--ko foreach: employeeVM.Tags -->
<span data-bind="text: $data.Tags"></span>
<!-- /ko -->
And no, there are no errors in the console, I have used KnockouJS context debugger which shows me that there are elements in this collection, even when I try to display them as an object it shows me a list of 4 elements.
Knockout version: 2.3.0
1). If you are binding masterVM object in ko.applyBindings(masterVM), you don't need to specify that object again in your data-bindings.
So, it should be
foreach: { data: employeeVM.Tags, as: 'tag' }
And not
foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }
(I'm not sure how the first data-bind="text: masterVM.employeeVM.Email" is working)
2). You don't need to call applyBindings more than once. If you want to update the employee object, you can turn your employeeVM into an observable and keep updating it inside getEmployeeDetails method.
3) Your containerless control flow syntax won't work. (<!--ko foreach: employeeVM.Tags -->). Inside this foreach, $data is the current Tag object in context. So, it should be <span data-bind="text: $data.TagName"></span>
Here's a minimal version of the code. Click on "Run code snippet" to test it. When you click on Update employee button, I'm updating the employeeVM observable and the data gets rendered again. Without calling applyBindings again
var employeeHandler = function() {
var self = this;
self.getEmployeeDetails = function(header) {
var newEmployee = new observableEmployee(0, 'newEmployee#xyz.com', [{
Id: 3,
EmployeeId: 3,
TagId: 3,
tagName: 'Tag Name 3'
}]);
// You need to use employeeVM(newEmployee) instead of employeeVM = newEmployee
// Because employeeVM is an observable.
masterVM.employeeVM(newEmployee);
}
}
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
}
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
}
var masterVM = {
controller: {
renderEmployeeDetails: ''
},
employeeHandler: new employeeHandler(),
// change this to an observable
employeeVM: ko.observable(new observableEmployee(0, 'abc#xyz.com', [{
Id: 1,
EmployeeId: 1,
TagId: 1,
tagName: 'Tag name 1'
}]))
}
ko.applyBindings(masterVM);
document.getElementById("button").addEventListener("click", function(e) {
masterVM.employeeHandler.getEmployeeDetails()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="field-group">
<label class="popup-label" for="email">Email:</label>
<span class="email" data-bind="text: employeeVM().Email"></span>
</div>
<ul data-bind="foreach: { data: employeeVM().Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.employeeId"></span> <br>
<span class="popup-tag" data-bind="text: tag.tagId"></span><br>
<span class="popup-tag" data-bind="text: tag.TagName"></span>
</li>
</ul>
<button id="button">Update employee</button>
I am trying to use postal.js subscribe/publish data in my reactJs site, I am currently doing this. Can anyone tell me how to push the selected id, I think the loadContacts method is resetting the value to false:
This is my top level page:
// load initial contacts into page
loadContacts: function() {
var page = this;
ContactDirectoryService.getContacts(this.state.pageNumber, function(response) {
var contacts = response.contacts.map(function(contact){
contact.isSelected = false;
return contact;
});
page.setState({ contacts: contacts });
});
},
// postal subscribe to receive publish
componentDidMount: function() {
this.loadContacts();
var page = this;
contactChannel.subscribe("selectedContact", function(data, envelope) {
page.handleSelectedContact(data.id, page);
});
},
handleSelectedContact: function(id, page) {
var page = this;
// service to add contact using api call
BasketService.addPerson(id, function () {
console.log(id);
var arrayPush = [];
var arrayPush = page.state.selectedContacts.slice();
// push selected id to selectedContacts array
arrayPush.push(id);
page.setState({selectedContacts: arrayPush})
//add is selected to contacts
page.setState({ contacts: contacts });
// push selected id which isn't working
for(var i=0;i<page.state.contacts.length;i++)
{
var idAsNumber = parseInt(id);
if (page.state.contacts[i].id === idAsNumber) {
page.state.contacts[i].isSelected = true;
break;
}
}
basketChannel.publish({
channel: "basket",
topic: "addContactToBasket",
data: {
id: id,
newTotal: arrayPush.length
}
});
});
},
addContactToBasket: function(selectedId) {
console.log('Add ID');
console.log('Add ID');
BasketService.addPerson(selectedId, function () {
var arrayPush = [];
var arrayPush = this.state.selectedContacts.slice();
arrayPush.push(selectedId);
this.setState({selectedContacts: arrayPush})
person.isSelected = true;
basketChannel.publish({
channel: "basket",
topic: "addContactToBasket",
data: {
id: selectedId,
newTotal: arrayPush.length
}
});
});
},
Checkbox component page, to select id and publish to selectedContact channel
handler: function(e) {
e.target.value;
e.preventDefault();
channel.publish({
channel: "contact",
topic: "selectedContact",
data: {
id: e.target.attributes['data-ref'].value
}
});
},
render: function() {
return (
<div className="contact-selector">
<input type="checkbox"
checked={this.props.data.isSelected}
onChange={this.handler} />
</div>
);
},
You're passing a cached context as page, however in the first line of handleSelectedContact() you're also reinitialising the argument page into a fresh local copy of this.
I'm starting to learn and azure phonejs.
Todo list get through a standard example:
$(function() {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
// Read current data and rebuild UI.
// If you plan to generate complex UIs like this, consider using a JavaScript templating library.
function refreshTodoItems() {
var query = todoItemTable.where({ complete: false });
query.read().then(function(todoItems) {
var listItems = $.map(todoItems, function(item) {
return $('<li>')
.attr('data-todoitem-id', item.id)
.append($('<button class="item-delete">Delete</button>'))
.append($('<input type="checkbox" class="item-complete">').prop('checked', item.complete))
.append($('<div>').append($('<input class="item-text">').val(item.text)));
});
$('#todo-items').empty().append(listItems).toggle(listItems.length > 0);
$('#summary').html('<strong>' + todoItems.length + '</strong> item(s)');
}, handleError);
}
function handleError(error) {
var text = error + (error.request ? ' - ' + error.request.status : '');
$('#errorlog').append($('<li>').text(text));
}
function getTodoItemId(formElement) {
return $(formElement).closest('li').attr('data-todoitem-id');
}
// Handle insert
$('#add-item').submit(function(evt) {
var textbox = $('#new-item-text'),
itemText = textbox.val();
if (itemText !== '') {
todoItemTable.insert({ text: itemText, complete: false }).then(refreshTodoItems, handleError);
}
textbox.val('').focus();
evt.preventDefault();
});
// Handle update
$(document.body).on('change', '.item-text', function() {
var newText = $(this).val();
todoItemTable.update({ id: getTodoItemId(this), text: newText }).then(null, handleError);
});
$(document.body).on('change', '.item-complete', function() {
var isComplete = $(this).prop('checked');
todoItemTable.update({ id: getTodoItemId(this), complete: isComplete }).then(refreshTodoItems, handleError);
});
// Handle delete
$(document.body).on('click', '.item-delete', function () {
todoItemTable.del({ id: getTodoItemId(this) }).then(refreshTodoItems, handleError);
});
// On initial load, start by fetching the current data
refreshTodoItems();
});
and it works!
Changed for the use of phonejs and the program stops working, even mistakes does not issue!
This my View:
<div data-options="dxView : { name: 'home', title: 'Home' } " >
<div class="home-view" data-options="dxContent : { targetPlaceholder: 'content' } " >
<button data-bind="click: incrementClickCounter">Click me</button>
<span data-bind="text: listData"></span>
<div data-bind="dxList:{
dataSource: listData,
itemTemplate:'toDoItemTemplate'}">
<div data-options="dxTemplate:{ name:'toDoItemTemplate' }">
<div style="float:left; width:100%;">
<h1 data-bind="text: name"></h1>
</div>
</div>
</div>
</div>
This my ViewModel:
Application1.home = function (params) {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
var toDoArray = ko.observableArray([
{ name: "111", type: "111" },
{ name: "222", type: "222" }]);
var query = todoItemTable.where({ complete: false });
query.read().then(function (todoItems) {
for (var i = 0; i < todoItems.length; i++) {
toDoArray.push({ name: todoItems[i].text, type: "NEW!" });
}
});
var viewModel = {
listData: toDoArray,
incrementClickCounter: function () {
todoItemTable = client.getTable('todoitem');
toDoArray.push({ name: "Zippy", type: "Unknown" });
}
};
return viewModel;
};
I can easily add items to the list of programs, but from the server list does not come:-(
I am driven to exhaustion and can not solve the problem for 3 days, which is critical for me!
Specify where my mistake! Thank U!
I suggest you use a DevExpress.data.DataSource and a DevExpress.data.CustomStore instead of ko.observableArray.
Application1.home = function (params) {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
var toDoArray = [];
var store = new DevExpress.data.CustomStore({
load: function(loadOptions) {
var d = $.Deferred();
if(toDoArray.length) {
d.resolve(toDoArray);
} else {
todoItemTable
.where({ complete: false })
.read()
.then(function(todoItems) {
for (var i = 0; i < todoItems.length; i++) {
toDoArray.push({ name: todoItems[i].text, type: "NEW!" });
}
d.resolve(toDoArray);
});
}
return d.promise();
},
insert: function(values) {
return toDoArray.push(values) - 1;
},
remove: function(key) {
if (!(key in toDoArray))
throw Error("Unknown key");
toDoArray.splice(key, 1);
},
update: function(key, values) {
if (!(key in toDoArray))
throw Error("Unknown key");
toDoArray[key] = $.extend(true, toDoArray[key], values);
}
});
var source = new DevExpress.data.DataSource(store);
// older version
store.modified.add(function() { source.load(); });
// starting from 14.2:
// store.on("modified", function() { source.load(); });
var viewModel = {
listData: source,
incrementClickCounter: function () {
store.insert({ name: "Zippy", type: "Unknown" });
}
};
return viewModel;
}
You can read more about it here and here.
This script's purpose is to load the data source into table first,
And then there is a search box to search data.
The dataSource only load at the first time page load,
How to make it reload the data source every 30 seconds, thanks
<TableSorter dataSource={"/welcome/get_winner_list"} config={CONFIG} />
Here's the whole script
/** #jsx React.DOM */
var CONFIG = {
sort: { column: "_id", order: "desc" },
filterText: "",
columns: {
_id: { name: "獎次"},
name: { name: "獎品"},
can_accept_prize_now: { name: "現領"},
staff_name: {name: "姓名"},
staff_id: {name: "工號"},
}
};
var TableSorter = React.createClass({
getInitialState: function() {
return {
items: this.props.initialItems || [],
sort: this.props.config.sort || { column: "", order: "" },
columns: this.props.config.columns,
filterText:this.props.config.filterText
};
},
componentWillMount: function() {
this.loadData(this.props.dataSource);
},
loadData: function(dataSource) {
if (!dataSource) return;
$.get(dataSource).done(function(data) {
console.log("Received data");
this.setState({items: data});
}.bind(this)).fail(function(error, a, b) {
console.log("Error loading JSON");
});
},
handleFilterTextChange: function (event){
this.setState({filterText:event.target.value})
},
editColumnNames: function() {
return Object.keys(this.state.columns);
},
receive_click:function(event){
name_staff=event.target.name.split(',')
dataSource='/take/'+name_staff[0]+'/'+name_staff[1];
$.getJSON(dataSource).done(function() {
console.log("Input success");
}.bind(this)).fail(function(error, a, b) {
console.log("Error :Input Prize");
});
itemsColumn=_.find(this.state.items,function(column){return column['_id']==name_staff[0]});
itemsColumn['taken_at']=true;
this.setState()
},
render: function() {
var rows = [];
var columnNames = this.editColumnNames();
var filters = {};
columnNames.forEach(function(column) {
var filterText = this.state.filterText;
filters[column] = null;
if (filterText.length > 0 ) {
filters[column] = function(x) {
if(x)
return (x.toString().toLowerCase().indexOf(filterText.toLowerCase()) > -1);
};
}
}, this);
var filteredItems = _.filter(this.state.items, function(item) {
return _.some(columnNames, function(c) {
return (!filters[c] || filters[c](item[c]));
}, this);
}, this);
var sortedItems = _.sortBy(filteredItems, this.state.sort.column);
if (this.state.sort.order === "desc") sortedItems.reverse();
var cell = function(x) {
return columnNames.map(function(c) {
if (c == 'taken_at') {
if (x[c])
return <td>
<span className="btn btn-shadow btn-danger" disabled="disabled">已領取</span>
</td>;
else
return <td>
<button name={x['_id']+','+x['staff_id']} className="btn btn-shadow btn-success" onClick={this.receive_click}>未領取</button>
</td>;
}
else if (c == 'can_accept_prize_now') {
if (x[c] )
return <td>
<img src="/images/check.png"></img>
</td>;
else
return <td>
<img src="/images/cross.png"></img>
</td>;
}
else
return <td>{x[c]}</td>;
}, this);
}.bind(this);
sortedItems.forEach(function(item) {
rows.push(
<tr key={item.id}>
{ cell(item) }
</tr>
);
}.bind(this));
var header = columnNames.map(function(c) {
return <th >{this.state.columns[c].name}</th>;
}, this);
return (
<div className="table">
<form>
<fieldset>
<input type="text" name="s" id="s" placeholder="搜尋方式:部分工號、部分姓名、部分獎次,皆可查詢。 Eg: 林 or 726 "
value={this.state.value} onChange={this.handleFilterTextChange}/>
</fieldset>
</form>
<p/>
<table cellSpacing="0" className="table table-striped table-advance table-hover prizes">
<thead>
<tr>
{ header }
</tr>
</thead>
<tbody>
{ rows }
</tbody>
</table>
</div>
);
}
});
var QueryString = function () {
// This function is anonymous, is executed immediately and
// the return value is assigned to QueryString!
var query_string = {};
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
// If first entry with this name
if (typeof query_string[pair[0]] === "undefined") {
query_string[pair[0]] = pair[1];
// If second entry with this name
} else if (typeof query_string[pair[0]] === "string") {
var arr = [ query_string[pair[0]], pair[1] ];
query_string[pair[0]] = arr;
// If third or later entry with this name
} else {
query_string[pair[0]].push(pair[1]);
}
}
return query_string;
} ();
var App = React.createClass({
render: function() {
return (
<div>
<TableSorter dataSource={"/welcome/get_winner_list"} config={CONFIG} />
</div>
);
}
});
$( document ).ready(function() {
console.log( "ready!" );
if(document.getElementById("searchWinner")){
React.render(<App />, document.getElementById("searchWinner"));
}
});
Update
I got the Uncaught TypeError: Cannot read property 'interval' of null
by the following code
getInitialState: function() {
return {
interval: 5,
items: this.props.initialItems || [],
sort: this.props.config.sort || { column: "", order: "" },
columns: this.props.config.columns,
filterText:this.props.config.filterText
};
},
componentDidMount: function() {
setInterval(function() {
this.setState({
interval: this.state.interval + 1
});
}.bind(this), 1000);
},
componentWillMount: function() {
this.loadData(this.props.dataSource);
},
var App = React.createClass({
render: function() {
return (
<div>
<TableSorter key={this.state.interval} dataSource={"/welcome/get_winner_list"} config={CONFIG} />
</div>
);
}
});
I suppose TableSorter is external component, so I suggest to force component to re-render using key property from parent component. Something like
componentDidMount: function() {
setInterval(function() {
this.setState({
interval: this.state.interval + 1
});
}.bind(this), 1000);
},
render: function() {
return <TableSorter key={this.state.interval} dataSource={"/welcome/get_winner_list"} config={CONFIG} />
}
Minus is that will reset any state inside TableSorter (paging, selected rows) after interval.
i can use this script:
setInterval(function() {
alert("alert every 3 second");
}, 3000);
EXAMPLE
I'm relatively new to Ember and JS frameworks and I'm facing difficulty in connecting a model to the Ember Table view/component.
With the code I currently have, the table is rendering but the rows are not being populated with any data. Obviously, I'm doing something wrong and falling short of understanding how to make this work properly.
I have tried to implement something similar to the technique used here and here but can't get it to work.
I wanted to use the FixtureAdapter to populate the model with data for now but later on move to retrieving the data from my backend API via Ajax/JSON.
Any help is greatly appreciated.
The JS part:
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true,
LOG_VIEW_LOOKUPS: true,
// LOG_ACTIVE_GENERATION: true,
LOG_BINDINGS: true
});
var stringAttr = DS.attr('string');
App.Item = DS.Model.extend({
name: stringAttr,
desc: stringAttr,
category: DS.attr('number'),
tags: stringAttr,
note: stringAttr
});
App.Item.FIXTURES = [
{ id: 0, name: 'XY Laptop', desc: 'test laptop', category: 1, tags: 'computers, laptops', note:"note"},
{ id: 1, name: 'ZZ Computer', desc: 'super computer!', category: 0, tags: 'computers', note:"note"},
{ id: 2, name: 'MM Radio', desc: 'FM Radio', category: 1, tags: 'radios, recorders', note:"note"}
];
App.Router.map(function() {
this.resource('inventory', function() {
this.resource('items', function() {
this.resource('item', { path: ':item_id' }, function() {
this.route('edit');
});
});
});
});
// Temporary buffer object that sites between Ember Table and the model object (the Item model object)
App.RowProxy = Ember.Object.extend({
object: null,
getObjectProperty: function(prop) {
var obj = this.get('object');
if(obj) { console.log(prop + " : " + obj.get(prop)); }
return obj ? obj.get(prop) : 'Loading ...';
},
isLoaded: function() { return !!this.get('object'); }.property('object'),
id: function() { return this.getObjectProperty('id'); }.property('object.id'),
name: function() { return this.getObjectProperty('name'); }.property('object.name'),
desc: function() { return this.getObjectProperty('desc'); }.property('object.desc'),
category: function() { return this.getObjectProperty('category'); }.property('object.category'),
tags: function() { return this.getObjectProperty('tags'); }.property('object.tags'),
note: function() { return this.getObjectProperty('note'); }.property('object.note')
});
App.LazyDataSource = Ember.ArrayProxy.extend({
requestPage: function(page) {
var content, end, start, url, _i, _results;
content = this.get('content');
start = (page - 1) * 3;
end = start + 3;
// find items and then update the RowProxy to hold the item object.
this.get('store').find('item').then(function(items) {
return items.forEach(function(item, index) {
var position = start + index;
content[position].set('object', item);
});
});
// fill the 'content' array with RowProxy objects
return (function() {
_results = [];
for (var _i = start; start <= end ? _i < end : _i > end; start <= end ? _i++ : _i--){ _results.push(_i); }
return _results;
}).apply(this).forEach(function(index) {
return content[index] = App.RowProxy.create({
index: index
});
});
},
objectAt: function(index) {
var content, row;
content = this.get('content');
row = content[index];
if (row && !row.get('error')) {
return row;
}
this.requestPage(Math.floor(index / 3 + 1));
return content[index];
}
});
App.ItemsController = Ember.Controller.extend({
hasHeader: false,
hasFooter: false,
rowHeight: 35,
numRows: 10,
store: null,
columns: Ember.computed(function() {
var columnNames, columns;
columnNames = ['Name','Desc','Category','Tags','Note'];
columns = columnNames.map(function(key, index) {
return Ember.Table.ColumnDefinition.create({
columnWidth: 150,
headerCellName: key.w(),
contentPath: key
});
});
return columns;
}).property(),
content: Ember.computed(function() {
return App.LazyDataSource.create({
content: new Array(this.get('numRows')),
store: this.get('store')
});
}).property('numRows')
});
While the requestPage method above is not strictly required now, it would be useful to use when I migrate to the RESTful API, I kept it.
The HTML template looks like this:
<script type="text/x-handlebars">
{{#link-to 'items'}}
<i class="icon-edit"></i>
Item Management
{{/link-to}}
<div class="page-content">
{{outlet}}
</div><!-- /.page-content -->
</script>
<script type="text/x-handlebars" data-template-name="items">
<div class="row">
{{table-component
columnsBinding="columns"
contentBinding="content"
}}
</div>
</script>
I wasn't too sure how to show the table but my attempt to define it via a View wasn't successful.
I'm using:
Ember 1.2.0
Handlebars 1.1.2
Ember-Data 1.0.0-beta.3
jQuery 2.0.3
Ember Table 0.0.2