Understanding Ember Views - model-view-controller

I'm looking for some clarification on views in Ember.js
Coming from a rails background and I'm trying to ignore any preconceptions. From what I understand of the ember framework there are 5 components:
Routes: This is where we define the state of the application. The state is reflected in the URL. We can also define data loading here. Route classes are defined and on startup ember creates route objects which last for the duration of the application.
Models: This is where object data is defined. Can also define computed properties. A model object is created for each json object returned from the server.
Controllers: This mediates interactions between the models and templates/views. Controller classes are defined and on startup ember creates controller objects which last for the duration of the application. There is only ever a single instance of each controller class.
Templates: These describe the generated markup.
Views: These are specific templates or dom elements relating to a model. These are used to define interface events and send them to the controller for handling. Not sure when to create these.
As an example lets say I have a EventsController that has data loaded on the applicationRoute:
ScheduleApp.EventsController = Ember.ArrayController.extend();
ScheduleApp.ApplicationRoute = Ember.Route.extend({
setupController: function() {
this.controllerFor('events').set('content', ScheduleApp.Event.find());
}
});
Now in my template instead of iterating over each and displaying the information I want to iterate over each and create an associated view so I can add interactions to each event. I presume I would need to create a new view for each event and have it display in my template. However, I'm not sure where I create these views. Do I define a view class and then ember will create a new view object each time I call it using the view helper? Eventually I would like to use the appendTo on the view to inject my events to different places in the dom. Where would this be defined?
I've tried reading over the ember.js guide for views but it describes the context of a creating a single view. I think I want to make many views for each event and then dynamically interact with those objects.
Up to now ember has been outrageously clever so I would assume there is a built in method for generating these views. After all, most user interfaces are full of lists that require interactions. The problem is the list I'm trying to make I then want to spread over the dom depending on its attributes.

As per your code, App.EventsController has a list of events, now let us say we want the UI to have a list of events displayed and for each event say we want the view to have a delete button which deletes the event when the user clicks
One way to accomplish is by using Ember.CollectionView, the collection view as the name suggests is tailored for these sort of requirements, in many Ember examples the usage of view is not defined because ember auto-generates it for you but in some cases we might need to explicitly define a view to meed our requirements
App.EventsView = Ember.CollectionView.extend({
// It needs a list of data to iterate upon
// We are binding it to the controllers content data which
// is a list of events
contentBinding: "controller.content",
appendSpan: function(){
view = Ember.View.create({tagName: 'span'});
this.get("childViews").forEach(function(child){
view.appendTo(child);
});
},
// Now we need to also define a view template that will be
// generated for all the elements in the content array
// This could in turn be another collection view if required
// I am going to keep it simple for now
itemViewClass: Ember.View.extend({
templateName: "event",
deleteEvent: function(){
// Implement Delete
this.get("content").deleteRecord();
},
notifyUser: function(){
// The record doesn't get deleted as soon as user clicks, the request goes to
// server and server deletes the record and sends back the response to the
// client, Hence I'm using an observer here on isDeleted property of the record
if(this.get('content.isDeleted')){
alert("Record deleted Successfully");
}
}.observes('content.isDeleted')
})
})
Important Note Inside the CollectionView definition this.get("content") refers to the array of events, while in itemViewClass this.get("content") refers to the single event object
//Defining the template
//Assuming the event model has a name property
<script type="text/x-handlebars" data-template-name="event">
Name: {{view.content.name}}
<a {{action deleteEvent target="view"}} href="#">Delete Event</a>
</script>
Now when you hit your application_url/events
you'll a list of events each event has a delete button, I hope this clears some concepts
For more information about CollectionView
Update as per the comment:
If you want to append another view to each child view, you can do so by editing the template of itemViewClass as follows
<script type="text/x-handlebars" data-template-name="event">
Name: {{view.content.name}}
<a {{action deleteEvent target="view"}} href="#">Delete Event</a>
{{ view App.SomeOtherView }}
</script>
it can be a partial too as follows
<script type="text/x-handlebars" data-template-name="event">
Name: {{view.content.name}}
<a {{action deleteEvent target="view"}} href="#">Delete Event</a>
{{ partial "somePartial" }}
</script>
or if you want to do it programmatically say, you click a button in the EventsView template and on click all the childs view must have a span tag appended to it(I am very bad at giving examples)
//This is the template for App.EventsController,
//template-name = "events" => view is App.EventsView and controller App.EventsController
<script type="text/x-handlebars" data-template-name="events">
<a {{action appendSpan target="view"}} href="#"> Append </a>
</script>
appendSpan is defined in the CollectionView

Related

Vuejs how to pass component data from caller

My main page renders a list of data coming from controller with foreach
#foreach ($sales as $sale)
<button id="{{$sale->id}}" #click="editClicked({{$sale}})">
#endforeach
I have an edit component placed on the page like this, I display it modally via showEditModal conditional
<edit v-if="showEditModal" #hide="showEditModal=false"></edit>
The component in brief is declared in Edit.vue:
<template name="edit">
...
</template>
This is simply a standard form with input fields, bound via v-model to the sale.
Essentially it is an update form, I intend to load the data from the main page into each input field to be edited.
My app.js simply sets showEditModal = true, in order to display the edit form on top of the main page.
Basically i don't want to have to call controller via GET method on loading the modal since i already have the data in the main page as $sale object, so im just wondering how do I pass in the $sale to the Edit.vue component ?
I thought that in the <edit> component usage, it would need to bind the sale object, however I'm unsure how that would work since it comes from the foreach loop.
I do, also have the data in the app.js method as passed in via #click="editClicked({{$sale}})", but again, i'm unsure how to use that to pass through ?
You're right, you would want to pass the current sale item as a property to the edit modal. What I would do is add a data property to your main Vue called selectedSale.
data:{
selectedSale: null
}
Then in your editClicked method, set selectedSale
methods:{
editClicked(sale){
this.selectedSale = sale
...
}
}
Finally, pass it as a property.
<edit :sale="selectedSale" v-if="showEditModal" #hide="showEditModal=false"></edit>

How do I auto fill field values in a section of a form that is loaded via ajax in Laravel 4?

I have a section of a form that dynamically loads different sets of fields based on the user's selection in a control. I'm using a javascript event handler to detect when the selection changes, and using AJAX (with HTML payload) to pull in the proper set of fields.
I would like to be able to use Laravel's Form::getValueAttribute() method to automatically fill in the form fields' values in both the static and dynamic form parts. However, the partial view that is loaded by my AJAX call does not have the same instance of the Form class as the view with my main Form, so I can't simply call getValueAttribute() in the partial.
My thought is to make the AJAX call a POST, and serialize the necessary data (a subset of Input::old() or the model data depending whether the page is loaded as the result of validation errors, or an UPDATE request) to send along with the POST so that the HTML fragment I get back has the values set properly.
Is this the best way to get what I want? If so, does Laravel have any tools to help with the serialization of form data? If not, what might be a better approach?
I've found an approach I like better. When the view is loaded normally I use AJAX as usual to load the partial. But when the view is loaded for a validation post-back or for editing, I use Laravel's Views' nest method to nest the partial view containing the proper fields directly into the response. The partial then has access to all the Input and error data I need. The user is still able to change the field set as usual but I put up a confirm prompt for them if they have already set some values in a field set they previously selected. If they decide to proceed anyway, the field set is cleared and a new field set is brought in via AJAX as usual.
My code looks something like this:
Controller:
public function newThing() {
if ( Request::session()->has('errors') ) {
// this is a validation post-back
return View::make('thing')
->nest('fields', 'fields_partial');
} else {
// just a normal unfilled form
return View::make('thing');
}
}
public function editThing() {
return View::make('thing')
->nest('fields', 'fields_partial');
}
View: thing.blade.php (just a snip of it)
...
<form>
...
<select id="picker">...</select>
<div class="sub-fields">
{{ isset($fields) ? $fields : '' }}
</div>
...
</form>
...
<script>
$('#picker').change(function() {
// if any .sub-fields inputs have been changed, get confirmation from the user
// if user confirms, do ajax stuff to replace .sub-fields contents with new field set
// otherwise cancel the change
});
</script>

Ember. How to delete a view's instance

Im struggling to delete a view's instance. On view hbs i use each loop to show view hbs. On another field click i add a " . " to a json object, which then adds another field to the template.
>js>App.ApplicationView = Ember.View.extend({
anotherField: [{name: 'testname'}],
actions: {
moreFields: function(){
this.get('anotherField').pushObject({name: ''});
},
less: function(){
var counter = this.get('anotherField');
counter.shift();
this.set('anotherField', counter);
And hbs
{{#each view.anotherField}}
{{view Ember.TextField}}
{{/each}}
<button {{action 'moreFields' target='view'}}> ++ </button>
<button {{action 'less' target='view'}}> -- </button>
http://jsbin.com/iSUdiCaX/17/edit
Cheers
Kristjan
When you use the shift method Ember doesn't get notified that the anotherField property changed, and therefore it doesn't update the template. You can check this by adding this.rerender() at the end of the less action.
You could:
call this.propertyDidChange('anotherField') to notify the property changed: http://emberjs.com/api/classes/Ember.Object.html#method_propertyDidChange
use the slice method: http://emberjs.com/api/classes/Ember.Array.html#method_slice
var sliced = this.get('anotherField').slice(0, this.get('anotherField').length - 1);
this.set('anotherField' sliced);
I also noticed you're using the View to handle the actions whereas I believe the Controller would be a better place to do so.
EDIT
Well it depends.... I believe the controllers are a good place because they have knowledge of the model (the view also has it via the controller). if your anotherField property is only needed for displaying or event handling logic then I believe it is a good idea to leave it in the view. from docs
Views in Ember.js are typically only created for the following
reasons:
When you need sophisticated handling of user events
When you want to create a re-usable component
But if instead the anotherField property is used the held application state (user selections, needed for computed properties or other actions) then I believe it's better placed inside the controller (and therefore the actions modifying it).
Have in mind your view can handle one part of the action and send it to the controller:
actions: {
something: function() {
.....
this.get('controller').send('something') // calls send action in controller
}
}
I hope this helps!

Form select box in Backbone Marionette

I'm trying using Backbone.Marionette to build an application. The application gets its data through REST calls.
In this application I created a model which contains the following fields:
id
name
language
type
I also created an ItemView that contains a complete form for the model. The template I'm using is this:
<form>
<input id="model-id" class="uneditable-input" name="id" type="text" value="{{id}}"/>
<input id="model-name" class="uneditable-input" name="name" type="text" value="{{name}}" />
<select id="model-language" name="language"></select>
<select id="model-type" name="type"></select>
<button class="btn btn-submit">Save</button>
</form>
(I'm using Twig.js for rendering the templates)
I am able to succesfully fetch a model's data and display the view.
What I want to do now is populate the select boxes for model-language and model-type with options. Language and type fields are to be restricted to values as a result from REST calls as well, i.e. I have a list of languages and a list of types provided to me through REST.
I'm contemplating on having two collections, one for language and one for type, create a view for each (i.e. viewLanguageSelectOptions and viewTypeSelectOptions), which renders the options in the form of the template I specified above. What I am not sure of is if this is possible, or where to do the populating of options and how to set the selected option based on data from the model. It's not clear to me, even by looking at examples and docs available, which Marionette view type this may best be realized with. Maybe I'm looking in the wrong direction.
In other words, I'm stuck right now and I'm wondering of any of you fellow Backbone Marionette users have suggestions or solutions. Hope you can help!
Create a view for a Select in my opinion is not needed in the scenario that you are describing, as Im assuming that your languages list will not be changing often, and the only porpouse is to provide a list from where to pick a value so you can populate your selects in the onRender or initializace function of your view using jquery.
you can make the calls to your REST service and get the lists before rendering your view and pass this list to the view as options and populate your selects on the onRender function
var MyItemView = Backbone.Marionette.ItemView.extend({
initialize : function (options) {
this.languages = options.languages;
this.typeList = options.typeList;
},
template : "#atemplate",
onRender : function () {
this.renderSelect(this.languages, "#languagesSelect", "valueofThelist");
this.renderSelect(this.typeList, "#typesSelect", "valueofThelist")
},
renderSelect :function (list, element, value) {
$.each(list, function(){
_this.$el.find(element).append("<option value='"+this[value]+"'>"+this[value]+"</option>");
});
}
})
var languagesList = getLanguages();
var typeList = getTypesList();
var myItemView = new MyItemView({languages:languagesList,typeList :typeList });
Hope this helps.

Separating template logic from Backbone.View

I just started learning Backbone.js, and have been working on (what else) a simple to-do application. In this app, I want to display my to-do items inside of <ul id="unfinished-taks"></ul> with each task as a <li> element. So far, so simple.
According to the tutorials I have read, I should create a View with the following:
// todo.js
window.TodoView = Backbone.View.extend({
tagName: 'li',
className: 'task',
// etc...
});
This works fine, but it seems like bad practice to define the HTML markup structure of my to-do item inside of my Javascript code. I'd much rather define the markup entirely in a template:
// todo.js
window.TodoView = Backbone.View.extend({
template: _.template($("#template-task").html()),
// etc...
});
<!-- todo.html -->
<script type="text/template" id="template-task">
<li class="task <%= done ? 'done' : 'notdone' %>"><%= text %></li>
</script>
However, if I do it that way Backbone.js defaults to using tagName: 'div' and wraps all my to-do items in useless <div> tags. Is there a way to have the HTMl markup entirely contained within my template without adding unsemantic <div> tags around every view element?
If you are only planning to render the view once, you can set the el property of the view manually in .initialize():
// todo.js
window.TodoView = Backbone.View.extend({
template: _.template($("#template-task").html()),
initialize: function() {
this.el = $(this.template(this.model.toJSON())).get(0);
},
// etc
});
There are some caveats here, though:
Backbone expects the el property to be a single element. I'm not sure what will happen if your template has multiple elements at the root, but it probably won't be what you expect.
Re-rendering is difficult here, because re-rendering the template gives you a whole new DOM element, and you can't use $(this.el).html() to update the existing element. So you have to somehow stick the new element into the spot of the old element, which isn't easy, and probably involves logic you don't want in .render().
These aren't necessarily show-stoppers if your .render() function doesn't need to use the template again (e.g. maybe you change the class and the text manually, with jQuery), or if you don't need to re-render. But it's going to be a pain if you're expecting to use Backbone's standard "re-render the template" approach for updating the view when the model changes.

Resources