Ember.js: View stops updating after rending the first two items - javascript

I have an ArrayController (List) with an itemController (ListItem). The problem I am seeing is that the template that renders the list stops updating after the first two list items are rendered.
In the code below, there are two setTimeout calls when the ArrayController's view is inserted into the DOM. Each of these setTimeout calls will connect to the ArrayController and add two more list items to it. The first setTimeout works fine: two list items are added to the ArrayController, and the template shows both list items. However, after the second setTimeout, the template still only shows the first two list items instead of all four. At this point, there are only two list items shown on the page, but if I console.log the length of the content array on the ArrayController, it shows the correct length of 4.
Templates:
<!-- List View -->
<script type="text/x-handlebars" data-template-name="list" id="list">
{{each controller itemViewClass="App.ListItemView"}}
</script>
<!-- Item View -->
<script type="text/x-handlebars" data-template-name="listitem" id="listitem">
<li>Testing: {{content.caption}}</li>
</script>
Controllers:
App.ListController = Ember.ArrayController.extend({
itemController: 'list-item',
addItem: function (caption) {
this.pushObject(
App.ListItem.create({
caption: caption
})
);
}
});
App.ListItemController = Ember.Controller.extend({
});
Views:
App.ListView = Ember.View.extend({
templateName: 'list',
didInsertElement: function () {
setTimeout(function () {
this.get('controller').addItem('test1');
this.get('controller').addItem('test2');
}.bind(this), 1000);
setTimeout(function () {
this.get('controller').addItem('test3');
this.get('controller').addItem('test4');
}.bind(this), 2000);
}
});
App.ListItemView = Ember.View.extend({
tagName: '',
templateName: 'listitem',
});
Model:
App.ListItem = Ember.Object.extend({
caption: ''
});
Now, if I change the blockless each helper into a regular each helper like the following, then this all works fine. But I want to be able to define a class for the view of each item.
<!-- List View -->
<script type="text/x-handlebars" data-template-name="list" id="list">
{{#each controller}}
<li>Testing: {{content.caption}}</li>
{{/each}}
</script>

As seen above, I had originally not assigned a tag to the list item template. After I assigned it a tag, it works.
I changed the item template to:
<!-- Item View -->
<script type="text/x-handlebars" data-template-name="listitem" id="listitem">
Testing: {{content.caption}}
</script>
And the item view to:
App.ListItemView = Ember.View.extend({
tagName: 'li',
templateName: 'listitem',
});

Related

Vue.js - Inconsistencies between model and rendered content

I have the following minimum example:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ol>
<li v-for="series in currentSeries" v-bind:data-id="series.id">
The ID is <% series.id %>
X
</li>
</ol>
</div>
</body>
<script>
vm = new Vue({
el: '#app',
delimiters: ["<%", "%>"],
data: {
currentSeries: [{id: "1"}, {id: "2"}, {id: "3"}]
},
methods: {
removeSeries: function(id) {
this.currentSeries = this.currentSeries.filter(function(element) {
return element.id != id;
});
}
}
});
$(function() {
$(document).on("click", ".removeSeries", function() {
var id = $(this).parent().data("id");
console.log(id);
vm.removeSeries(id);
});
});
</script>
</html>
I have a list of series in the variable currentSeries. I create a list of these, with adding an -tag to each item to remove it from the list.
If I click on the first 'X', the first element is removed from the list and ID 1 is shown in the console. Then, if I again click on the first element, it should remove the second element (which is now the first one). HOwever, the output id is still 1, i.e. the data-id was not updated of the node during the rendering.
What's the problem here and how to improve on this?
You are mixing jQuery with Vue and both are conceptually different. Here jQuery is entirely unnecessary because you can use Vues built in click event:
// This will call remove series on click and remove the element by the given id
X
Now, this will call your removeSeries for the given id and you can update the underlying model data however you want.
Here's the JSFiddle:
https://jsfiddle.net/vyra5nzc/
Just a note on delimiters, Vue also accepts #{{data}} as a delimiter if you are using the double mustache in some other templating engine such as laravels blade.

After using jQuery UI to sort an Ember.js item, using Ember Data's model.deleteRecord() doesn't work

I'm using jQuery UI Sortable with Ember.js to sort a list of items, and it seems to work great, until I go to delete one of the Ember Data records. The model is deleted properly, but the UI doesn't update to reflect that. If you delete the last record, an Index Out of Range error is thrown. If you delete a middle record, the one after it is removed from the DOM. If you delete the first record, it removes the first and second one from the DOM. What gives?
Given the following Handlebars:
<script type="text/x-handlebars" data-template-name="application">
<h1>Ember Data + jQueryUI Sortable Problems</h1>
{{outlet}}
Try sorting and then deleting something. The model is deleted properly, but the DOM does not reflect that. Sometimes it stays in the DOM, sometimes the wrong one is deleted, and sometimes both the right and a wrong one is deleted.
</script>
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
{{render 'formField' this}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="formField">
<div class="form-field" {{bind-attr data-id=id}}>
<span class="delete" {{action 'delete'}}>X</span>
{{name}} ({{displayOrder}})
<span class="handle">#</span>
</div>
</script>
And the following JavaScript:
App.IndexController = Ember.ArrayController.extend({
sortProperties: ['displayOrder'], // Force sort by displayOrder, not ID
updatePositions : function(positionData){
this.propertyWillChange('content'); // Manually notify Ember
this.get('content').forEach(function(formField) {
var key = formField.get('id');
formField.set('displayOrder', positionData[key] + 1);
});
this.propertyDidChange('content'); // Manually notify Ember
}
});
App.IndexView = Ember.View.extend({
handleSort: function(event,ui){
var positionData = {};
this.$(".form-field").each(function(index, element){
// Get model ID from bind-attr in template
var key = $(element).data('id');
positionData[key] = index;
});
this.get('controller').updatePositions(positionData);
// Delay recreating the sortable
Ember.run.next(function(){ this.makeSortable(); }.bind(this));
},
makeSortable: Ember.on('didInsertElement', function(){
try {
this.$().sortable("destroy");
} catch(err){
window.console.warn('No sortable to destroy', err);
}
finally {
this.$().sortable({
handle: '.handle',
axis: 'y',
update: this.handleSort.bind(this)
});
}
})
});
App.FormFieldController = Ember.ObjectController.extend({
actions: {
'delete': function() {
window.console.log('deleting record', this.get('name'));
this.get('model').deleteRecord();
}
}
});
Here's a fiddle.
The trick is in your use of sortProperties and {{#each model}} or {{#each content}}. The sortProperties method does not actually arrange content or model, it arranges arrangedContent. By changing it to {{#each arrangedContent}}, your problems disappear because the DOM arrangement will stay in sync with your model arrangement.

How would you achieve multiple drag and drop with ember

I've seen different examples of single object drag and drop like referenced in this question Ember.js + HTML5 drag and drop shopping cart demo
But since the drag event is on the view object, I don't se how I would achieve multiple view selection drag and drop (aka like in an email client or in evernote for instance).
Any jsbin is more than welcome.
This is an example of using drag and drop using ember along with jquery-ui . Although it is not necessary to split the draggable components into separate views, they have been split just to demonstrate the multiple view selection mentioned by the op.
So combine the following code with the example found in this thread
How do I drag multiple elements at once with JavaScript or jQuery?
(look at the comments http://jsfiddle.net/zVZFq/358/)
http://emberjs.jsbin.com/sasasuka/1/edit
hbs
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each post in model}}
{{#with post}}
<div class="placeholder">
{{render "post" post}}
</div>
{{/with}}
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="post">
<div class="post" {{bind-attr id="id"}}>
{{name}}
</div>
</script>
js
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return allPosts;
}
});
App.IndexView = Ember.View.extend({
classNames:["post-container"]
});
App.PostController = Ember.ObjectController.extend({
});
App.PostView = Ember.View.extend({
templateName:"post",
classNameBindings: ['selected'],
selected:Ember.computed.alias("context.selected"),
didInsertElement:function(){
this.$(".post").draggable({ revert: "invalid", snap: ".post-container",snapMode:"inner" });
var self = this;
/*jquery ui create the draggable component*/
this.$(".post").draggable({ revert: "invalid", snap: ".post-container",snapMode:"inner" });
/*create the droppable component*/
this.$().droppable({
drop:function(event,ui){
var draggedPostId = parseInt(ui.draggable.attr("id"),10);
var draggedPost = self.get("parentView").get("controller").findBy("id",draggedPostId);
var draggedOrder = draggedPost.get("order");
var droppedPost = self.get("controller").get("model");
var droppedOrder = droppedPost.get("order");
draggedPost.set("order",droppedOrder);
droppedPost.set("order",draggedOrder);
allPosts = allPosts.sortBy("order");
self.get("parentView").get("controller").set("model",allPosts);
}
});
},
click:function(){
this.toggleProperty("controller.selected");
}
});
App.Post = Ember.Object.extend({
id:null,
name:null,
order:null
});
/*this would come from a server or web storage*/
var allPosts = [];
allPosts.pushObject(App.Post.create({id:1,name:"post1",order:1}));
allPosts.pushObject(App.Post.create({id:2,name:"post2",order:2}));
allPosts.pushObject(App.Post.create({id:3,name:"post3",order:3}));
allPosts.pushObject(App.Post.create({id:4,name:"post4",order:4}));
allPosts.pushObject(App.Post.create({id:5,name:"post5",order:5}));
I'm using jqueryUI for this. Add the file below into your app and then you can extend the custom jquery views!
https://gist.github.com/jamesmgg/9191149

Jquery knockout dynamic template not working

I have a view model:
function ViewModel() {
this.applications = ko.observableArray();
this.templateView = ko.observable("application-grid");
this.templateToUse = function () {
return this.templateView();
}.bind(this);
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
I have a list that is binded to the viewModel
<ul data-bind="template: { name: templateToUse, foreach: applications }"></ul>
When the page loads, it firs selects the "application-grid" template id.
When i change it first time, viewModel.templateView('application-list');, the template changes.
Then, if i change it back, viewModel.templateView('application-grid');, the template doesn't change anymore.
I am doing something wrong?
If you want to use knockout templates then you can specify that in bindings:
<ul data-bind="template: { name: templateToUse, foreach: applications, templateEngine: new ko.nativeTemplateEngine() }"></ul>
by default knockout will use jquery templates if it is referenced on page. You could also use other templates, for more info see documentation.

In the Ember Todo example, how would I toggle rather than remove the todos?

I'm referring to this example
http://emberjs.com/examples/todos/
In the code they clear the list by filtering and then removing from the collection
clearCompletedTodos: function() {
this.filterProperty('isDone', true).forEach(this.removeObject, this);
},
Suppose I wish to apply the same filter but I want to toggle rather than remove. In other words some type of 'Hide Done' button. If checked it would hide all of the done tasks. If unchecked it would show all of the done tasks. How would I do this?
You can create something along this lines, see http://jsfiddle.net/pangratz666/rufjC/
Handlebars:
{{#each todos}}
{{#view App.TodoView todoBinding="this" }}
{{view Ember.Checkbox valueBinding="todo.isDone" titleBinding="todo.label" }}
{{/view}}
{{/each}}
JavaScript:
App.TodoView = Ember.View.extend({
hideTodosBinding: 'App.hideTodos',
isDoneBinding: 'todo.isDone',
isVisible: function(){
return !(this.get('hideTodos') && this.get('isDone'));
}.property('hideTodos', 'isDone').cacheable()
});

Categories

Resources