I'm using mustache to render a series of images. Each image is accompanied by a text input for setting the image's caption, an update button for updating the caption in the database via Ajax, and a delete button for deleting the image from the database (also via Ajax):
{{#logos}}
<div class="logo">
<div class="logo-input">
<input type="text" placeholder="Caption" value="{{caption}}" />
</div>
<div class="logo-buttons">
<button>Update</button>
<button>Delete</button>
</div>
</div>
{{/logos}}
I'm new to backbone, and I can't figure out how to "associate" each update/delete button with its corresponding logo/caption. What's the best way to do this, considering that the number of images is unknown at run time?
You have two problems:
How do you know which button is pressed?
How do you know which logo you're working with?
The easiest way to solve the first problem is to attach a class to the buttons:
<button type="button" class="update">Update</button>
<button type="button" class="delete">Delete</button>
Then you can bind the events directly to the buttons using the view's events:
events: {
'click .update': 'update_caption',
'click .delete': 'delete_caption'
}
Also, you should always specify the type attribute when using <button>, the spec says that <button type="button"> is the default but some browsers use <button type="submit"> instead. If you always specify type="button" you don't have to worry about what sort of nonsense the browsers will get up to.
Now you have to figure out which logo you're working with.
One way is to keep using a single view and attach a data attribute somewhere easy to find. For example:
{{#logos}}
<div class="logo" data-logo="{{id}}">
<!-- ... -->
</div>
{{/logos}}
Then you can do things like this in the click handlers:
update_caption: function(ev) {
var id = $(ev.currentTarget).closest('.logo').data('logo');
var logo = this.collection.get(id);
//...
}
Demo: http://jsfiddle.net/ambiguous/DwkPV/
Alternatively, you could use one sub-view for each logo. Here you'd have one view per-logo:
var LogoView = Backbone.View.extend({
className: 'logo',
events: {
'click .update': 'update_caption',
'click .delete': 'delete_caption'
},
//...
});
and a template without the {{#logos}} loop or the outer per-logo <div>:
<div class="logo-input">
<input type="text" placeholder="Caption" value="{{caption}}" />
</div>
<div class="logo-buttons">
<button>Update</button>
<button>Delete</button>
</div>
and the click handlers would simply look at this.model:
update_caption: function() {
var logo = this.model;
//...
}
Then a main view to iterate over the logos and render the subviews:
var LogosView = Backbone.View.extend({
render: function() {
this.collection.each(function(logo) {
var v = new LogoView({ model: logo });
this.$el.append(v.render().el);
}, this);
return this;
},
//...
});
Demo: http://jsfiddle.net/ambiguous/9A756/
Related
I am new to angular, searched for a good solution for the below but couldnt find a good option.
I have an extremelly sipmple modal dialog controlled by ModalDialogCtrl that contains an edited object, such as Rabbit or Dog or Cat or anything else. I want same functionality for any object allowing Save when user presses "Save" button.
Dialog's viewmodel has a nested view for the object being edited whose template name is substituted depending on the type of edited object. This specific view contains object-specific controller.
Modal controller:
function ModalDialogCtrl($scope) {
// $scope.objectSpecificViewModelTemplate = "rabbit.html";
// or
// $scope.objectSpecificViewModelTemplate = "dog.html";
// etc
ctrl.save = function () {
// need to call inner object controller's save() method here
};
ctrl.cancel = function () {
// cancel editing
};
};
Modal dialog view:
<div class="modal-header">
<!-- Modal header -->
</div>
<div class="modal-body" id="modal-body">
<!-- Modal body containing object-specific view model -->
<div ng-include src="objectSpecificViewModelTemplate"></div>
</div>
<div class="modal-footer">
<!-- Modal buttons -->
<button class="btn btn-primary" type="button">OK</button>
<button class="btn btn-warning" type="button">Cancel</button>
</div>
Object-specific view templates:
<div ng-controller="RabbitCtrl">
<p>Weight: <input type="text" ng-model="rabbit.weight" /></p>
</div>
or
<div ng-controller="DogCtrl">
<p>Color: <input type="text" ng-model="dog.color" /></p>
</div>
Object-specific controllers:
function RabbitCtrl($scope) {
$scope.rabbit = { weight: 5}
$scope.save = function() { /* save to server */ };
}
function DogCtrl($scope) {
$scope.dog = { dog: "black"}
$scope.save = function() { /* save to server */ };
}
What I need is to call inner object's save() method when user presses Save button. And I want the modal controller and object-specific controllers be decoupled as I might want to reuse them in different spots of the application. So I think gennerally my question looks like: how to make parent controller to call specific nested controller method (there can be many nested controllers) or how to make inner controller to call specific parent's controller method?
I see too ways here:
Use events.
Use require. In child controller you require parent and call i.e.:
parentCtrl.register(childCtrl)
Now in parentCtrl you store link to child:
vm.register = function(child) {
vm.containedComponent = child;
}
and can call any method of it. (i.e. onSave)
(This is not that bad if you sure that your parent will always have exactly one child, however if child may change using ng-if you will need to manually unregister it)
is it possible to trigger the popover directive on an event? I'm trying to look for a string character and trigger a custom template, however, I cannot find a way to get my way around it, I only see custom attributes attached to a button.
You can use popover-is-open to display the popover on a given event.
Here is an example, where a timeout is used to simulate an event that shows the popover:
Markup:
<div ng-controller="PopoverDemoCtrl as vm">
Wait for 3 seconds for the event to happen...
<div uib-popover="Read the message!"
popover-title="Hello World!"
popover-placement="bottom"
id="popover"
class="btn btn-default spaced"
popover-is-open="vm.showPopover">
Popover
</div>
</div>
JavaScript:
function PopoverDemoCtrl($timeout) {
var popoverDemoCtrl = this;
popoverDemoCtrl.showPopover = false;
$timeout(function () {
popoverDemoCtrl.showPopover = true;
}, 3000);
}
PopoverDemoCtrl.$inject = ['$timeout'];
angular
.module('myApp', ['ui.bootstrap'])
.controller('PopoverDemoCtrl', PopoverDemoCtrl);
The full Fiddle: http://jsfiddle.net/masa671/gtgqof2k/
I'm trying to create a set of radio buttons that I want to exhibit toggling behavior (i.e., the selected button should be highlighted). I'm using the following partial template:
<h3>{{fullAddress()}}</h3>
<h4 class="control-label">Result of Visit</h4>
<pre>{{visit}}</pre>
<div class="btn-group" data-toggle="visitState">
<div ng-repeat="option in visitOptions()" class="btn btn-default" ng-value="option.Value" ng-model="$parent.visit">
{{option.Label}}
</div>
</div>
The controller is quite simple:
app.controller("visitCtrl", function($scope, dataContext) {
var _visit = "NotHome";
$scope.visit = "NotHome";
$scope.visitOptions = function() {
return dataContext.visitOptions();
};
$scope.fullAddress = function() {
var home = dataContext.home();
var pin = dataContext.pin();
if( home.Unit == "" ) return pin.StreetAddress;
return pin.StreetAddress + " #" + home.Unit;
};
});
dataContext.visitOptions() just returns an array of {Label, Value} objects.
As things stand, there is no toggling behavior. Then again, the model value (visit) doesn't update when you click any of the buttons, either :).
For the benefit of others, there were two problems with my code:
I was including jQuery, which conflicts with the toggling behavior. I had to remove jQuery and include ui-bootstrap to replace the event-driven functionality bootstrap depends uses.
Tying ng-model to a "root-level" property (i.e., $scope.visit) keeps the auto-updating/two-way data binding functionality of angular from working. I had to tie ng-model to visit.result instead:
$scope.visit = {
result: "NotHome",
hadQuestion: false,
notes: null,
};
The final markup was pretty simple:
<div class="btn-group" data-toggle="visitState">
<div ng-repeat="option in visitOptions()" class="btn btn-primary" btn-radio="option.Value" ng-model="visit.result">
{{option.Label}}
</div>
</div>
I've noticed that in the ionic todo app example the stale/old todo information remains on the modal if I cancel the modal and open it back up again. What's the best place to clear/reset the old modal data so that it always has fresh blank fields after I cancel or submit the modal form fields?
Should I null or clear the task object somehwere? Reset the fields manually on close and create? Add a handler to some sort of on hide event?
Here's the angular/ionic example:
http://ionicframework.com/docs/guide/building.html
and a relevant snippet of code
// Called when the form is submitted
$scope.createTask = function(task) {
$scope.tasks.push({
title: task.title
});
$scope.taskModal.hide();
task.title = "";
};
// Open our new task modal
$scope.newTask = function() {
$scope.taskModal.show();
};
// Close the new task modal
$scope.closeNewTask = function() {
$scope.taskModal.hide();
};
and the modal
<div class="modal">
<!-- Modal header bar -->
<ion-header-bar class="bar-secondary">
<h1 class="title">New Task</h1>
<button class="button button-clear button-positive" ng-click="closeNewTask()">Cancel</button>
</ion-header-bar>
<!-- Modal content area -->
<ion-content>
<form ng-submit="createTask(task)">
<div class="list">
<label class="item item-input">
<input type="text" placeholder="What do you need to do?" ng-model="task.title">
</label>
</div>
<div class="padding">
<button type="submit" class="button button-block button-positive">Create Task</button>
</div>
</form>
</ion-content>
I've had the same problem. I first tried to clear my form data by clearing the model-object upon closing my modal window, just like you, but that only worked for when I submitted the form, it seems. When cancelling, it doesn't work! (Even if you explicitly clear the object before hiding the popup, it will not work)
I eventually fixed it by doing this:
$scope.newTask = function() {
$scope.task = {};
$scope.taskModal.show();
};
This way, every time the window is loaded, you clear the model. So the trick is not to do it when submitting data, but when opening the modal window. That did it for me at least.
Btw, I also needed an edit function for this same modal window, so I do this:
$scope.editTask = function(task) {
$scope.task = task;
$scope.taskModal.show();
};
The accepted answer is definitely correct but there is another way to achieve the same goal.
// Execute action on hide modal
$scope.$on('modal.hidden', function() {
// Execute action
$scope.task = {};
});
I'm trying to put together an email list template Using Handlebars.js with requirejs and Backbone.js, the initial rendering shows up as expected - a single email input with an add icon to add more.
var EmailView = bb.View.extend({
tagName: 'ul',
className: 'emailList',
events: {
"click .addEmail" : "addEmail",
"click .deleteEmail" : "deleteEmail"
},
initialize : function () {
this.template = hb.compile(hbTemplate);
},
render : function () {
this.$el.htmlPolyfill(this.template(this.model.toJSON()));
this.updateIcons();
return this;
},
...
The addEmail handler (I've tried appendPolyfill(), appendPolyFillTo, and the current updatePolyFill(). All produce the same results. The new line item is added, but all placeholders disappear (this is true for controls outside of $el, it appears to be the whole page.)
addEmail : function(e) {
this.$el.append( this.template({}) );
this.$el.updatePolyfill();
this.updateIcons();
}
What I want is for existing controls to maintain their placeholder text and the new one added showing the placeholder text as well. What am I missing?
If it helps, template looks like this ...
<li>
<span class="requiredPrompt">*</span>
<img class="icon" alt="" src="/images/emailIcon.png" />
<input type="email" class="emailAddress" value="" placeholder="Email Address" maxlength="50" required/>
<a class="deleteEmail" href="javascript:void(0)">
<img class="icon" alt="" src="/images/delFile.png" />
</a>
<a class="addEmail" href="javascript:void(0)">
<img class="icon enabled" alt="" src="/images/addFile.png" />
<img class="icon disabled" alt="" src="/images/addFile-disabled.png" />
</a>
</li>
As a quick fix you can simply return false or preventDefault() on your click handler. Here is a modfied jsfidlle.
jQuery('.addEmail').click(function () {
jQuery('.emailList').appendPolyfill(emailItem);
return false;
});
Webshims thinks the page unloaded and clears all placeholders.