http://emberjs.jsbin.com/aGUguJA/10
Using the bloggr example I built a list of posts and a post view and I've added 2 actions to the post template, Previous and Next.
App.PostController = Ember.ObjectController.extend({
actions:{
next: function(){
//Go to next object in a model
},
prev: function(){
//Go to previous object in a model
}}
});
I can't figure out how to make the Previous and Next to work. I have a strong feeling I need to be using an ArrayController but I still wouldn't know where to go from there.
One simple solution would be to add two fields/variables in your post object i.e.
{
id: '1',
title: "Object 1",
author: { name: "Awesome Person 1" },
date: new Date('01-01-2013'),
excerpt:"Lorem ..",
body:"Lore.."
next:2,
prev:null
}
So you either have this information available in your data or create App.Post objects that get instanciated from the data and you write a simple iteration to populate the fields within the model part of your App.PostsRoute.
Then you can modify your template accordingly and pass the object or id from the link-to of next and previous buttons.
look at a working example,
http://emberjs.jsbin.com/OxajiVi/1/
EDIT
The following example demostrates the solution using Ember class and objects to create the linked list, no previous and next fields in json data and also works when visiting a post directly,
http://emberjs.jsbin.com/uWAmUba/1
This could be helpful:
<button {{action "next"}}>Next</button>
Related
In backend, my object relationship is that an Item has_many Options. I'd like to be able to access all the attributes on the item and its child options as a hash in the front end:
items = [
{
id: 1,
item_attribute_name: item_attribute_value,
options: [
{id: 1, option_attribute_name: option_attribute_value},
{id: 2, option_attribute_name: option_attribute_value},
]
},
{
id: 2,
item_attribute_name: item_attribute_value,
options: []
}
]
I'm sending data either via a json object in response to an ajax request or using the handy gon gem. I noticed that if I were JUST interested in sending the parent items, the formatting automatically happens such that I can just send back Item.all and in the front end, get an array of items with each item being a hash that represents its attributes exactly as I want.
But if I want to send the children is there a standard way of doing it? I realize I can construct the child attributes myself as below, but wondering if there's a more straight forward direct way.
How I would make this work by constructing the child attributes:
items = Item.all
items.each do |i|
child_attr = {"options" => i.options }
i.attributes.merge(child_attr)
end
A totally acceptable answer, by the way, is that there's no... automagical way of doing this without doing what I'm doing now, which is converting each parent object to attributes in backend, and then stitching together the child attributes.
I'm only asking this question, frankly, because it'd be nice to keep the object relationships in the backend for reuse elsewhere, if possible, rather turning things into a hash.
I think the only way to get the expected result is with some monkey patching. So you can use a serializer, or include, or use ActiveModel::Serializers::JSON which is included by default in your models.
For exemple with ActiveModel::Serializers::JSON, you can do something like:
items = Item.all
items.map! do |i|
i.serializable_hash(include: { options: {} })
end
This is due to rails eager loading, which avoids having to load all children from an association(has_many for example). Where you'd like to serialize is up to your use case.
I've written a component called Upload which allows users to upload files and then report back with a JSON object with these files. In this particular instance, the Upload component has a parameter which comes from a parent view model:
<upload params="dropzoneId: 'uploadFilesDropzone', postLocation: '/create/upload', uploadedFiles: uploadedFiles"></upload>
The one of importance is called uploadedFiles. The parameter binding here means I can reference params.uploadedFiles on my component and .push() new objects onto it as they get uploaded. The data being passed, also called uploadedFiles, is an observableArray on my parent view model:
var UploadViewModel = function () {
// Files ready to be submitted to the queue.
self.uploadedFiles = ko.observableArray([]);
};
I can indeed confirm that on my component, params.uploadedFiles is an observableArray, as it has a push method. After altering this value on the component, I can console.log() it to see that it has actually changed:
params.uploadedFiles.push(object);
console.log(params.uploadedFiles().length); // was 0, now returns 1
The problem is that this change does not seem to be reflected on my parent viewmodel. self.uploadedFiles() does not change and still reports a length of 0.
No matter if I add a self.uploadedFiles.subscribe(function(newValue) {}); subscription in my parent viewmodel.
No matter if I also add a params.uploadedFiles.valueHasMutated() method onto my component after the change.
How can I get the changes from my array on my component to be reflected in the array on my parent view model?
Why do you create a new observable array when the source already is one? You can't expect a new object to have the same reference as another one: simply pass it to your component viewModel as this.uploads = params.uploads. In the below trimmed-down version of your example, you'll see upon clicking the Add button that both arrays (well the same array referenced in different contexts) stay in sync.
ko.components.register('upload', {
viewModel: function(params) {
this.uploads = params.uploads;
this.addUpload = function() { this.uploads.push('item'); }.bind(this);
},
template: [
'<div><button type="button" data-bind="click: addUpload">Add upload</button>',
'<span data-bind="text: uploads().length + \' - \' + $root.uploads().length"></span></div>'].join('')
});
var app = {
uploads: ko.observableArray([])
};
ko.applyBindings(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="component: {name: 'upload', params: {uploads: uploads}}"></div>
It is only in case your source array is not observable that things get a little more complicated and you need to have a manual subscription to update the source, eg. you would insert the following in the viewModel:
this.uploads.subscribe(function(newValue) { params.uploads = newValue; });
Additionally the output in the text binding would not be updated for the source because it is not observable. If for some reason that I cannot conceive of you would want to have 2 different observableArrays (1 source & 1 component), you should still be able to do with the line above, but replace the function code with params.uploads(newValue)
The problem may be related to this bug (to be confirmed): https://github.com/knockout/knockout/issues/1863
Edit 1: So this was not a bug. You have to unwrap the raw param to access the original observable. In your case, it would be:
params.$raw.uploadedFiles() //this would give you access to the original observableArray and from there, you can "push", "remove", etc.
The problem is that when you pass a param to a component, it gets wrapped in a computed observable and when you unwrap it, you don't have the original observableArray.
Reference: http://knockoutjs.com/documentation/component-custom-elements.html#advanced-accessing-raw-parameters
While Binding Property that involves Parent --> Child Relation
Use Binding in this way
If You want to bind data to Child Property
data-bind='BindingName : ParentViewmodel.ChildViewModel.ObservableProperty'
Here it seems you want to subscibe to a function when any data is pushed in Array for that you can write subscribe on Length of Observable array which can help you capture event that you want.
This should solve your problem.
Using Ember, we have a list of shoes which is fetched from a database. These are listed at '/shoes.
this.resource('shoes', function() {
this.route('new');
this.route('show', {path: ':shoe_id'});
this.route('edit', {path: ':shoe_id/edit'});
});
Only the first 10 shoes in the MongoDB collection are listed in the view, as specified in our webb API. When creating a new shoe (using the nested route 'new'), and transitioning back to '/shoes', the new shoe is added to the current 'shoes' model.
export default Ember.ObjectController.extend({
actions: {
save: function() {
this.get('model').save();
this.transitionToRoute('shoes');
}
}
});
This results in a list of 11 shoes. In other words, it does not use the route and make a new API call. Instead, it is added to the current list of shoes in the model. When refreshing the page, the result is rendered as intended, fetching the 10 first records of the DB collection.
We would like to make the ’transitionToRoute’ execute the route and re-fetch the model instead of just adding it to the current model. We have seen a few examples of how ’this.refresh()’ and ’this.reload()’ can be used inside the controller's 'model' scope body but these examples have not worked for us.
Is it possible to make a ’transitionToRoute’ refresh the model with new database values using the 'shoes' route?
Based on what you wrote, I'm guessing you're trying to use pagination and only want the first 10 shoes to be listed on your /shoes route?
If so, the "Ember Way" is to always keep all your models in sync and never have to do special work just to get the view to update artificially. In this case, Ember has a local store of shoes where it initially has 10 items. Then you add one more, it gets saved both the database and to the Ember local store and so now Ember thinks (correctly) that you have 11 shoes. Just because Mongo returns 10 shoes doesn't mean your entire data set is 10 shoes.
So, the best way to handle this situation is to have your view display an accurate projection of your underlying model data. In other words, don't tell your view to display "all shoes". Tell it to display a "filtered list of all shoes"!
In practice, I've seen two types of filtering on ArrayController. One is just to return the first n values. For that use good old javascript slice (See MDN docs). The second is to use the Ember filter function. See Ember Docs.
Ultimately, your controller would something like this:
Shoes Controller:
export default Ember.ArrayController.extend( PaginatorClientSideMixin, {
shoesFilteredOption1: function() {
return this.get('arrangedContent') // 'arrangedContent' is the sorted list of underlying content; assumes your backing model is the DS.RecordArray of shoes
// this use of slice takes an array and returns the first 10 elements
.slice( 0, 10 );
// we depend on 'arrangedContent' because everytime this changes, we need to recompute this value
}.property('arrangedContent')
shoesFilteredOption2: function() {
return this.get('arrangedContent') // 'arrangedContent' is the sorted list of underlying content; assumes your backing model is the DS.RecordArray of shoes
// here we're filtering the array to only return "active" shoes
.filter( function(item, index, self ) {
if (item.isActive) { return true; }
})
}.property('arrangedContent')
});
Then on your Handlebars template read from shoesFilteredOption1 instead of content or model.
I have an application that saves a user's search criteria in localStorage, where each saved search is represented as an instance of an Ember.js model:
Checklist.SavedSearch = DS.Model.extend({
id: DS.attr('string'),
filters: DS.attr('string')
});
When the "save" button is pressed, the controller creates a model instanced and creates a record for it:
Checklist.savedSearchController = Ember.ArrayController.create({
[..]
save: function(view) {
var saved_seach = Checklist.SavedSearch.createRecord({
id: 'abcd',
filters: '<json>'
});
Checklist.local_store.commit();
}
});
Checklist.local_store is an adapter I created (this is unsurprisingly where the problem probably begins) that has a basic interface that maps createRecord, updateRecord, etc. to a bunch of get/set methods that work with localStorage (loosely based on a github fork of ember-data). The adapter appears to work fine for some basic tests, particularly as findAll has no issues and returns values added manually to localStorage.
Here is the relevant method within Checklist.local_store:
createRecord: function(store, type, model) {
model.set('id', this.storage.generateId);
var item = model.toJSON({associations: true});
this.storage.setById(this.storage_method, type, id, item);
store.didCreateRecord(model, item);
}
The problem is that when createRecord is called by the controller, absolutely nothing occurs. Running it through the debugger, and logging to console, seems to show that the method isn't called at all. I imagine this is a misunderstanding on my part as to how Ember.js is supposed to work. I'd appreciate help on why this is happening.
I come from a ruby and php background, and have perhaps foolishly dived straight in to a JS framework, so any other comments on code style, structure and anything in general are welcome.
Ember Data doesn't change createRecord on the controller so it shouldn't behave any differently. It's possible that there was something related to this in the past, but it's certainly not the case anymore.
I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)