ember js this.set() breaking bootstrap plugins - javascript

on didInsertElement i have initialised bootstrap popover and it works fine until i run an action i.e submit a form, after i save the form data on db i make a request to get the current saved data from api and then i use this.set() to update the model in realtime for the user... however after i use this.set() the popover breaks... to explain it a little better i'm gonna use an example below:
<form {{action 'saveForm' on='submit'}}>
{{input type="text" value=firstName class="form-control" placeholder="Firstname"}}
<button type="submit" class="btn btn-success" >Submit</button>
</form>
{{#each firstname in model.firstNames}}
<span data-toggle="popover" data-title="Firstname" data-content="{{unbound firstname}}" data-placement="top">Firstname</span>
{{/each}}
after using this.set() the popover inside #each doesn't work anymore..
UPDATE: this is the action where i call this.set()
App.firstNamesController = App.AppController.extend({
actions: {
updateFirstnames: function () {
$.getJSON('/api/firstnames/get/', function (jsonResponse) {
this.set('firstNames', jsonResponse.data.firstNames);
}.bind(this));
}
}
});
UPDATE #2:
App.firstNamesView = Ember.View.extend({
templateName: 'firstNamesTemplate',
didInsertElement: function() {
$('span[data-toggle="tooltip"]').tooltip({
trigger: 'click'
});
}
});

You'll need to (as Jeff mentioned) re-initialize your listeners after you set new names. didInsertElement isn't going to run every time new elements enter the DOM, it's just going to run when the view has been inserted for the first time once it's elements are ready.
Re-setting firstNames is adding new elements to the DOM after you set your listeners, so they aren't going to have the listeners set on them.
Untested solution:
App.firstNamesController = App.AppController.extend({
actions: {
updateFirstnames: function() {
$.getJSON('/api/firstnames/get/', function(jsonResponse) {
this.set('firstNames', jsonResponse.data.firstNames);
// add this after setting names:
Ember.run.next(this, function () {
$('span[data-toggle="tooltip"]').tooltip();
});
}.bind(this));
}
}
});
Also, your selector in your jQuery is targeting span[data-toggle="tooltip"] but your template has span[data-toggle="popover"] - is that a typo?
EDIT 1
Another option could be to observe the model.firstNames property and have the listeners get set any time model.firstNames changes. But observers can be a pain to manage.
// in your controller:
toolTipObserver: function () {
Ember.run.next(this, function () {
$('span[data-toggle="tooltip"]').tooltip();
});
}.observes('model.firstNames')

Related

Bootstrap-confirmation not respecting options

Just adding the bootstrap-confirmation extension for Bootstrap popover to some buttons on a project. I'm having issues with the options not being respected. I'm trying to get the popups to work as singletons and dismiss when the user clicks outside of them singleton and data-popout options, respectively - both set to true. I'm also not seeing any of my defined callback behavior happening.
I defined the options both in the HTML tags and in a function and neither works. Still getting multiple boxes and they don't dismiss as expected.
My JS is loaded after all other libraries and is in my custom.js file in my footer.
JS is as follows:
$(function() {
$('body').confirmation({
selector: '[data-toggle="confirmation"]',
singleton: true,
popout: true
});
$('.confirmation-callback').confirmation({
onConfirm: function() { alert('confirm') },
onCancel: function() { alert('cancel') }
});
});
An example of the box implemented on a button in my HTML is the following:
<a class="btn btn-danger" data-toggle="confirmation" data-singleton="true" data-popout="true"><em class="fa fa-trash"></em></a>
Any pointers would be appreciated. I even changed the default options in the bootstrap-confirmation.js file itself to what I want and still no luck.
Turns out I needed to rearrange a couple things to get this to work. I've left in the last_clicked_id etc stuff as I needed to add that to get the id value of what I'd just clicked.
// Product removal popup logic
var last_clicked_id = null;
var last_clicked_product = null;
$('.btn.btn-danger.btn-confirm').click(function () {
last_clicked_id = $(this).data("id");
last_clicked_product = $(this).data("product");
});
$('.btn.btn-danger.btn-confirm').confirmation({
singleton: true,
popout: true,
onConfirm: function () {
alert("DEBUG: Delete confirmed for id : " + last_clicked_product);
// TODO: Add AJAX to wipe entry and refresh page
},
onCancel: function () {
alert("DEBUG: Delete canceled for id : " + last_clicked_product);
}
});
I was a step ahead of myself with the callback logic which was not getting executed. Fixed by simply adding it to onConfirm: and onCancel: key values in the .confirmation() function. A bit of a RTFM moment there but this was unfortunately not very clear in the documentation.

Meteor: instantiate a Session without the click event

Using Meteor 0.9+.
Is there a way to instantiate a session as soon as the page renders?
I have a dynamic list of names that display upon clicking a .li element using the click event. This is fine. But I would like the user now to see at least one list, i.e. as if they have already clicked one of the .li elements when they land on the page.
Template.nameList.events({
'click li.title': function(e) {
e.preventDefault();
Session.set('postId', this._id);
var selectedId = Session.get('postId');
}
});
You could use template.created or template.rendered callback:
Template.nameList.rendered = function() {
Session.set('postId', this.data.someId);
};
You could also use IR onBeforeAction callback:
NameListRouter = RouteController.extend({
onBeforeAction: function() {
Session.set('postId', this.params.someId);
};
});

Meteor JS: What is the best way to store states for a specific template instance?

I'm learning about Session and reactive data sources in Meteor JS. They work great for setting global UI states. However, I can't figure out how to scope them to a specific instance of a template.
Here's what I'm trying to do
I have multiple contenteditable elements on a page. Below each is an "Edit" button. When the user clicks on the Edit button, it should focus on the element and also show "Save" and "Cancel" buttons.
If the user clicks "Cancel", then any changes are eliminated, and the template instance should rerender with the original content.
Here's the code I have so far
// Helper
Template.form.helpers({
editState: function() {
return Session.get("editState");
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get("editState")) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", null);
},
});
// Template
<template name="form">
<div class="{{editState}}">
<p class="form-field" contenteditable>
{{descriptionText}}
</p>
</div>
Edit
Save
Cancel
</template>
// CSS
.edit-btn
.cancel-btn,
.save-btn {
display: inline-block;
}
.cancel-btn,
.save-btn {
display: none;
}
.is-editing .cancel-btn,
.is-editing .save-btn {
display: inline-block;
}
The problem
If I have more than one instance of the Form template, then .form-field gets focused for each one, instead of just the one being edited. How do I make so that only the one being edited gets focused?
You can render a template with data, which is basically just an object passed to it when inserted in to a page.
The data could simply be the key to use in the Session for editState.
eg, render the template with Template.form({editStateKey:'editState-topForm'})
you could make a handlebars helper eg,
Handlebars.registerHelper('formWithOptions',
function(editStateKey){
return Template.form({editStateKey:editStateKey})
});
then insert it in your template with
{{{formWithOptions 'editState-topForm'}}} (note the triple {, })
Next, change references from Session.x('editState') to Session.x(this.editStateKey)/ Session.x(this.data.editStateKey)
Template.form.helpers({
editState: function() {
return Session.get(this.editStateKey);
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get(this.data.editStateKey)) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, null);
},
});
Note: if you are using iron-router it has additional api's for passing data to templates.
Note2: In meteor 1.0 there is supposed to be better support for writing your own widgets. Which should allow better control over this sort of thing.
As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.
Alternative approaches
1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:
'click .js-edit-action': function(event, t) {
var $this = $(event.currentTarget),
container = $this.parents('.phenom-comment');
// open and focus
container.addClass('editing');
container.find('textarea').focus();
},
'click .js-confirm-delete-action': function(event, t) {
CardComments.remove(this._id);
},
2 ReactiveVar scoped to template instance
if (Meteor.isClient) {
Template.hello.created = function () {
// counter starts at 0
this.counter = new ReactiveVar(0);
};
Template.hello.helpers({
counter: function () {
return Template.instance().counter.get();
}
});
Template.hello.events({
'click button': function (event, template) {
// increment the counter when button is clicked
template.counter.set(template.counter.get() + 1);
}
});
}
http://meteorcapture.com/a-look-at-local-template-state/
3 Iron-Router's state variables
Get
Router.route('/posts/:_id', {name: 'post'});
PostController = RouteController.extend({
action: function () {
// set the reactive state variable "postId" with a value
// of the id from our url
this.state.set('postId', this.params._id);
this.render();
}
});
Set
Template.Post.helpers({
postId: function () {
var controller = Iron.controller();
// reactively return the value of postId
return controller.state.get('postId');
}
});
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#setting-reactive-state-variables
4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.
5 update the data context
Session is often the worse choice in my opinion. Also I don't personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as "Flow".

Backbone Views UI

I am wondering if there is a better way of doing this.
I have some HTML that needs some events attaching to it.
Question 1:
There is no data, models or collections behind it so I assume no need for a render method?
Question 2:
I am assuming it should be a view in backbone because it is a single piece of UI that needs code attaching to it?
Basically what I have is a panel with show and hide functionality, which shows some check boxes for saving settings. When the panel closes it will save the states of the check boxes.
Here is the HTML:
<div id="panel-holder">
<div id="settings">
<ul class="settingsChecks">
<li>
<label>Display Desktop Notification Popup </label>
<input type="checkbox" id="formDisplayPopup" checked="checked"/>
</li>
<li>
<label>Play Alert Sound </label>
<input type="checkbox" id="formPlaySounds" checked="checked"/>
</li>
</ul>
</div>
</div>
So the above code is attached to a view using #panel-holder.
Here is the Backbone code:
var SettingsView = Backbone.View.extend({
el: '#panel-holder',
events: {
'click #click': 'toggleContent'
},
initialize: function() {
this.toggleContent();
},
showmeState: true,
toggleContent: function(){
if (this.showmeState === false) {
this.openPanel();
} else {
this.closePanel();
}
},
closePanel: function() {
this.$el.find('#settings').slideUp('fast');//Close Panel
this.$el.find('#click').text("Open Settings");//Change Text
this.showmeState = false;
this.saveSettings();
},
openPanel: function() {
this.$el.find('#settings').slideDown('fast');//Open Panel
this.$el.find('#click').text("Close Settings");//Change Text
this.showmeState = true;
},
saveSettings: function() {
//when the panel closes get the states of the checkboxes and save them
}
});
Question 3:
Should I be using jQuery .find('') in the open and close panel areas? Is there a better way of attaching functionality to these elements.
Question 4:
Is this code and my understanding of Backbone Views ok? Am I way off course?
Answers
I would include a render method that returns itself so that you can append the view to the #panel-holder element
Yes, this is fine
Always use $.find("") when working within a view's elements. Just index the elements so you can access them as this.$settings, etc
No looks good generally
Code
Here is my recommended code in Coffeescript (sorry I'm lazy):
Also uses Handlebars library to compile templates
tmpl = ....all your html...
SettingsView = Backbone.View.extend
attributes:
id: 'settings'
template: Handlebars.compile tmpl
events:
'click #click': 'toggleContent'
initialize:
#toggleContent()
showMeState: true
toggleContent: ->
#showMeState is false then #openPanel()
else #closePanel()
closePanel:->
#$settings.slideUp('fast')
#$click.text('Open Settings')
#showMeState = false
#saveSettings()
openPanel: ->
#$settings.slideDown('fast')
#$click.text('Close Settings')
#showMeState = true
saveSettings: ->
render: ->
#$el.append #template
#Store settings and click
#$settings = #$el.find('#settings')
#$click = #$el.find('#click')
#
settings = new SettingsView
$('#panel-holder').append settings.render().el
Question 1: There is no data, models or collections behind it so I
assume no need for a render method?
For now it is not required. But if you want to add extra functionality or if the UI changes in any way later then you might be requiring one.
Question 2: I am assuming it should be a view in backbone because it
is a single piece of UI that needs code attaching to it?
Yes. it is a view.
Question 3: Should I be using jQuery .find('') in the open and close
panel areas? Is there a better way of attaching functionality to these
elements.
this.$el.find('#settings') is perfectly fine.
You can also use either $('#settings', this.$el) or this.$('#settings')
But remember that you are using ID selectors . And ID will be unique on the page. So you do not need to find it in the context of the view
$('#setting') should be good enough
Question 4: Is this code and my understanding of Backbone Views ok? Am
I way off course?
You are doing perfectly well. But the thing is in backbone you can get a task done in many ways which works. And you need to choose the best approach amongst them. You will get to know the more you work with it. So do not worry about it.
The same can be condensed into a single method that will take care of toggling
var SettingModel = Backbone.Model.extend({
defaults: {
showmeState : false
}
});
var settingModel = new SettingModel();
var SettingsView = Backbone.View.extend({
el: '#panel-holder',
events: {
'click #click': 'toggleState'
},
initialize: function () {
// Listen to the Model that triggers
this.listenTo(settingModel, 'change', this.toggleContent);
this.model.trigger('change');
},
toggleState: function() {
//When Clicked just negate the boolean so that it triggers the change event
this.model.set('showmeState', !this.model.get('showmeState'));
},
toggleContent: function() {
var check = !this.model.get('showmeState'),
txt = check === true ? "Close Settings" : "Open Settings";
$('#click').text(txt);
check === true ? $('#settings').slideUp('fast')
: $('#settings').slideDown('fast');
if(check)
saveSettings();
},
saveSettings: function () {
//when the panel closes get the states of the checkboxes and save them
}
});
If you think check for condition is an overkill, then a simple if should be sufficient.

Trigger function when user changes input value (backbone.js)

I just started with backbone.js and is currently making a page which has a div #listing_filter containing text input elements and a table showing some listings fetched from a RESTful backend (PHP/Codeigniter). The values in the text input will act as filters to narrow down the results retrieved by the server.
Problem: Whenever the value in any of the text boxes changes, I want the browser to GET another set of results based on the filter values. Below is my attempt, where the updateFilter function does not fire even though the text input value was changed. Any ideas what went wrong?
Another question will be whether I should put the contents of #listing_filter into a template, or just hardcode it into the main body HTML? Thanks!
JS Code
window.ListingFilterView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'updateFilter');
},
events: {
'change input' : 'updateFilter'
},
updateFilter: function() {
console.log('hey');
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes: {
'': 'listings',
},
listings: function() {
//... some other code here
this.listingFilterView = new ListingFilterView();
}
});
HTML Code
<div id="listing_filter">
Price: <input type="text" id="listing_filter_price" />
Beds: <input type="text" id="listing_filter_bedroom" />
Baths: <input type="text" id="listing_filter_bathroom" />
</div>
A Backbone view will only respond to events on the view's el or on elements that are inside its el. By default, the view's el is just an empty <div> and you're not telling the view to use anything else.
You can tell the view which el to use (http://jsfiddle.net/ambiguous/5yXcU/1/):
new ListingFilterView({ el: $('#listing_filter') })
// Or like this:
new ListingFilterView({ el: '#listing_filter' })
or you can let the view attach itself to that element using setElement (http://jsfiddle.net/ambiguous/APEfa/1/):
initialize: function() {
this.setElement($('#listing_filter'));
// or setElement('#listing_filter')
}
or set the el when you define the view (http://jsfiddle.net/ambiguous/nyTZU/):
window.ListingFilterView = Backbone.View.extend({
el: '#listing_filter',
//...
});
Once that's out of the way, you'll need to understand that an <input> won't trigger a change event until it loses focus so you'll have to wait for the user to tab-out of the input or switch to listening for keypress events and using a timer to detect when they've stopped typing.

Categories