I'm currently learning how to deal with backbone.js.
Currently I have the problem that the model isnt updating when the view does through user input.
When I change the input of the textarea and I click the button, I send the old data since the view didnt notify the model that a change happened.
What do I do wrong?
PostView
define([
'jquery',
'underscore',
'backbone',
'postModel'
], function($, _, Backbone, PostModel){
var PostView = Backbone.View.extend({
timer: null,
el: $('.admin__wrapper'),
template: $('#post_template'),
initialize: function () {
console.log('Initializing Post View');
this.model = new PostModel({id: this.id});
this.listenTo(this.model, 'change', this.render);
this.model.fetch();
},
events: {
'keyup textarea': 'keyup',
'click button': 'submit'
},
submit: function(event) {
this.model.save();
},
render: function(){
var template = _.template(this.template.html(), { post: this.model });
this.$el.html(template);
return this;
}
});
return PostView;
});
PostTemplate
<script type="text/template" id="post_template">
<div class="post__title">
<div class="entry">
<input class="input--less" name="title" type="text" value="<%= post.get('title') %>" placeholder="Post title">
</div>
</div>
<div class="entry post__panel post__markdown">
<header class="entry__header post__panel__header">
<span><small>markdown</small></span>
</header>
<div class="post__boundary">
<textarea name="content" id="editor" class="input--less post__editor" rows="5"><%= post.get('content') %></textarea>
</div>
</div>
<div class="entry post__panel post__html">
<header class="entry__header post__panel__header">
<span><small>preview</small></span>
<span id="reading-time" class="float--right" style="text-align: right"></span>
</header>
<div class="post__boundary">
<div class="post__preview" id="preview"><%= post.get('content_compiled') %></div>
</div>
</div>
<div class="post__footer">
<span class="batch--tag-4 post__icon"></span>
<button class="button button--blue post__button">Publish</button>
</div>
</script>
When the user clicks submit your not getting the data from the view you are simply saving the model with defaults, therefor not firing any change events, you will have to do something like this in keyup method which is triggered by the keyup event.
keyup: function(e)
{
this.model.set(e.target.name, e.target.value, {silent: true});
}
Assuming you correctly named your textarea, the correct model attribute will be updated. So now when this.model.save is called by submit these attributes will be saved triggering the change event and re-rendering the view.
When you change the form in your page, this change is not automatically set in your model, you have to set it by yourself.
Backbone does not provide 2 way data binding like Angular Js, you need to set the model yourself and then sync the model.
Try Backbone Mutators for 2 way data binding in Bakcbone.
Also as a follow up read this article
Related
I am trying to create a select box in a modal that has a list of all cars in inventory. If I enter the app from a page that has all the data loaded and open the modal it works correctly. However if I am on a route that doesn't have the data loaded, then open the modal the select options do not show. Furthermore the options will never update from that point. How do I get the proper data to load? Should not this work since I am fetching the data direct from the store?
Dms.SellDialogController = Ember.ArrayController.extend({
cars: function() {
return this.store.find('car').filterProperty('isSold', false);
}.property('model'),
selectedCar: '',
carObjects: function() {
var cars = this.get('cars').map(function(car) {
return obj = {id: car.get('id'), key: car.get('keyNumber'), label: 'KEY#:'+car.get('keyNumber') + ' - STOCK#:' + car.get('stock')+' '+car.get('year')+' '+car.get('vModel')};
});
cars.sort(function(a, b){return a.key-b.key;})
return cars;
}.property('route'),
title: 'Select the car you are selling'
});
ApplicationRoute...
...
openModal: function(modalName, model) {
this.render(modalName, {
into: 'application',
outlet: 'modal',
model: model
});
},
Action to open modal inside application template
{{action 'openModal' 'sellDialog' model}}
and the templates
<script type="text/x-handlebars" id="components/modal-dialog">
<div class='modal-overlay' {{action "closeModal" target=cntrllr}}>
<div class='modal' {{action "dngn" target=cntrllr bubbles=false}}>
<div class='modal-content'>
<div class='modal-header'>
<button type="button" class="close" aria-label="Close" {{action "closeModal" target=cntrllr}}><span aria-hidden="true">×</span></button>
<h4 class="modal-title">{{title}}</h4>
</div>
{{yield}}
</div>
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="sellDialog">
{{#modal-dialog title=title cntrllr=controller model=model}}
<div class="modal-body">
<span class='pull-left'>Key Number:
{{view "select" content=carObjects optionValuePath="content.id" optionLabelPath="content.label" selection=selectedCar}}
</span>
</div>
<div class="modal-footer">
<button {{action "modalAction" controller "sell" selectedCar.id}}>Sell</button>
<button {{action "closeModal"}}>Cancel</button>
</div>
{{/modal-dialog}}
</script>
UPDATE 2015/5/4:
I have a hack to make it work for now. I added a cars property in the application route and I am getting it in SellDialogController. This looks like a bug to me. I will make a jsFiddle when I get some time and check it further before issuing a bug report.
try using setupController on the Application Route, since setupController is always called.
import Ember from 'ember';
export default Ember.Route.extend({
model: Ember.RSVP.hash({
optionsForSelect: function() {
// fetch your select data here
},
otherData: function() {
// get your regular model data here
}
}),
setupController: function(controller, model) {
this.controllerFor('sellDialog').set('model', model.optionsForSelect);
this.controller.set('model', model.otherData);
}
});
I have a rather sophisticated template for Kendo ListView using knockout-kendo.js bindings. It displays beautifully. My problem is that I need to use the visible and click bindings in parts of the template, but I can't get them to work. Below is a simplified version of my template. Basically, deleteButtonVisible determines whether the close button can be seen, and removeComp removes the item from the array.
<div class='template'>
<div >
<div style='display:inline-block' data-bind='visible: deleteButtonVisible, event: {click: $parent.removeComp}'>
<img src='../../../Img/dialog_close.png'></img>
</div>
<div class='embolden'>#= type#</div><div class='label1'> #= marketArea# </div>
<div class='label2'> #= address# </div>
<!-- more of the same -->
</div>
The view model:
function CompViewModel() {
var self = this;
self.compData = ko.observableArray().subscribeTo("compData");
self.template = kendo.template(//template in here);
self.removeComp = function (comp) {
//do something here
}
}
html:
<div class="row" >
<div class="col-md-12 centerouter" id="compDiv" >
<div class="centerinner" id="compListView" data-bind="kendoListView: {data: compData, template: template}"></div>
</div>
</div>
finally, sample data:
{
type: "Comparable",
marketArea: "",
address: "2327 Bristol St",
deleteButtonVisible: true
},
Take in count that the deleteButtonVisible must be a property on the viewModel linked to the view.You are not doing that right now. The click element can v¡be access from the outer scope of the binding and remove the $parent.He take the method from the viewmodel. Take in count that every thing that you take on the vie must be present on the view model for a easy access.
I'm trying to use twitter bootstrap, backbone and marionette for a form. I want a label and a select next to it. I was able to display the select but now am having trouble adding a label before the select element.
var app = new Marionette.Application();
app.addRegions({
region1: "#region1"
});
//.. model and collection here
app.typeView = Marionette.ItemView.extend({
tagName: "option",
template: "#item",
initialize: function(options){
this.$el.attr('value', options.model.get('id'));
}
});
/* COLLECTION VIEWS */
app.listview = Marionette.CollectionView.extend({
tagName: "select",
id: 'type-select',
itemView: app.typeView,
});
app.on("initialize:after", function() {
//somelist is defined here
var lv = new app.listview({
collection: somelist
});
app.region1.show(lv);
});
Here is the html
<div id="region1" class="container">
</div>
<script type="text/template" id="item">
<%= name %>
</script>
I want it to look like:
<div id="region1" class="container">
<label for="type-select">Name</label>
<select id="type-select"><option>...</option></select>
</div>
But it always looks like:
<div id="region1" class="container">
<select id="type-select"><option>...</option></select>
</div>
Try using a composite view:
<script type="text/template" id="select-field">
<label for="type-select">Name</label>
<select id="type-select"></select>
</script>
app.listview = Marionette.CompositeView.extend({
template: "#select-field",
itemView: app.typeView,
itemViewContainer: "select"
});
Essentially I have a list of objects, and the user is able to add new ones by typing into a textbox. As the user types, the new object appears. However, when the user clicks save, the object disappears from the listing. It is successfully saved to the database (Ruby on Rails/SQLite) so it appears on a full refresh. Why is it doing this, and how can I get it to stay on the screen when the user presses submit?
Here is some relevant code:
index.hbs
<div class="messages">
{{#each userhashtag in controller}}
{{render "userhashtag" userhashtag}}
{{/each}}
{{#link-to 'userhashtags.new'}} Add Hashtag{{/link-to}}
</div>
{{outlet}}
show.hbs
{{userhashtag.name}} <button class="btn btn-dancer" type="submit" {{action "destroy"}}>Remove</button><br>
new.hbs
<form role="form">
<fieldset>
<div class="form-group">
{{view Ember.TextField valueBinding='name' class='form-control' name='name' viewName='nameField'}}
</div>
<div class="btn-group">
<button type="submit" {{action "save"}} class="btn btn-success">Submit</button>
</div>
route
App.UserhashtagsNewRoute = Ember.Route.extend({
model: function(){
return this.store.createRecord('userhashtag');
},
setupController: function(controller, model){
controller.set('model', model);
},
renderTemplate: function() {
this.render('app/templates/userhashtags/new');
},
actions: {
save: function(){
return this.controller.get('model').save().then(function(){
this.transitionTo('userhashtags');
}.bind(this));
}
}
});
Make sure that you are manually pushing the new model into your Array collection (not sure how you're managing the collection). I'd suggest moving the #save action into a controller where you manage the list of objects.
// assuming "list" is some reference to your collection list within your controller
save: function() {
this.get('userhashtag').save().then(function(_model){
list.pushObject(_model);
I want to build simple page where user can select one photo from list and modify some properties, so i had built the model:
model = {
newPhotos: [],
currentPhoto: {
id: 0,
description: ""
},
setCurrent: function (photo, e) {
ko.mapping.fromJS(ko.mapping.toJS(photo), viewModel.currentPhoto);
}
}
and markup:
<div class="content" data-bind="foreach: newPhotos">
<div class="photo" data-bind="click: $parent.setCurrent">
<div class="frame" data-bind="img: imageSrc">
</div>
</div>
</div>
<div class="edit-area" data-bind="with: currentPhoto">
<canvas id="image-edit" />
<textarea class="multi-line" name="PhotoDesc" data-bind="value: description"></textarea>
</div>
Main idea is that when user click on photo, setCurrent function called and there i can update currentPhoto with view model that click binding pass into setCurrent, but there is problem ko.mapping.fromJS(jsObj, viewModel) (as i can see from source) expecting that viewModel will be root model.
I know that i can manually go through all observables and refresh their values, or unmap rootModel, set property and then update root, but i belive that there is more complex and elegant way to do this.
Thank you.
Set up your current photo as an observable, and then you can use map to load the observable with JSON details.
self.currentPhoto(ko.mapping.fromJS(data));
Here's a working JSFiddle to demonstrate:
http://jsfiddle.net/jearles/wgZ59/7/