KnockoutJS/Bootstrap - Clearing modal form when closing modal using javascript - javascript

I have a Bootstrap Modal that contains a form for updating or creating an entity (Company in my example). Right now my issue is that if I view an entity using the modal, it doesn't clear out the fields when I close the modal by any means. Causing the form to still be populated if I then click a "Create" button, which should bring me up a blank modal.
How can I execute one of my ViewModels methods from just regular javascript? Here is some of my code:
function ViewModel() {
var self = this;
function CompanyViewModel(company) {
var self = this;
self.Id = company.CompanyId;
self.Name = company.Name;
}
function BlankCompanyViewModel() {
var self = this;
self.Id = 0;
self.Name = "";
}
self.company = ko.observable();
self.companies = ko.observableArray();
self.clearCurrentCompany = function() {
self.company(new BlankCompanyViewModel());
};
// Initialize the view-model
$.getJSON("/api/company", function(companies) {
$.each(companies, function(index, company) {
self.companies.push(new CompanyViewModel(company));
});
self.clearCurrentCompany();
});
}
Ideally I'd like to run ViewModel.clearCurrentCompany on the "Hidden" event of the modal like so:
$('#myModal').on('hidden', function() {
//Do something here, not sure what
});

I like to use a custom binding around a modal to make it open/close/display based on populating/clearing an observable.
Something like:
ko.bindingHandlers.modal = {
init: function(element, valueAccessor, allBindings, vm, context) {
var modal = valueAccessor();
//init the modal and make sure that we clear the observable no matter how the modal is closed
$(element).modal({ show: false, backdrop: 'static' }).on("hidden.bs.modal", function() {
if (ko.isWriteableObservable(modal)) {
modal(null);
}
});
//apply the template binding to this element
ko.applyBindingsToNode(element, { with: modal }, context);
return { controlsDescendantBindings: true };
},
update: function(element, valueAccessor) {
var data = ko.utils.unwrapObservable(valueAccessor());
//show or hide the modal depending on whether the associated data is populated
$(element).modal(data ? "show" : "hide");
}
};
You then use this against an observable. It acts like a with binding against that observable and shows/hides the modal based on whether the observable is populated.
Here is a sample that shows this in use and sets up a subscription where you could run custom code when the modal is closed. http://jsfiddle.net/rniemeyer/uf3DF/

function ViewModel() {
var self = this;
// your previous code
$('#myModal').on('hide', function() {
self.clearCurrentCompany();
});
}
Just like that. Note that you want hide, not hidden, because hidden fires only after the modal completely disappears. If a user opens a create before the previous view closes, it will still be populated.

Related

Getting the index of the currently dragged item in Backbone.CollectionView

Given a panel
var panel = new Backbone.CollectionView({...})
How do I get the current model being sorted?
panel.on('sortStart', function(e) {
var index = something;
});
I suppose you use some kind of UI manipulation tool for example jQuery UI. As Lesha said in her comment it can be done through triggering of event on the model view.
//creting children view
var PanelItem = Backbone.View.extend({
events: {
"sortStart": "sortEventPropagation"
},
initialize : function (options) {
this.parentView = options.parentView;
},
sortEventPropagation: function(){
this.parentView.trigger('sort:start:propagated', this.model);
},
})
Everytime you are creating panelItem view you need to pass it panel in options as parentView.
var childView = new PanelItem({
parentView: panel
})
And on panel you could easily listenTo sort:start:propagated event
var Panel = Backbone.CollectionView.extend({
initialize: function(){
this.listenTo(this, 'sort:start:propagated', function(model){
//Do magic with model
})
},
})

Access values after form-pre-filling (with Ember.js)

I have one form for saving and editing records. On clicking on a record, the form should be filled with the data. After filling, I want to do some UI actions (call jQuery Plugin etc.).
The pre-filling works, but when I'm trying to access the values, it works only at the second click. On the first click, the values are empty or the ones from the record clicked before.
This action is stored in the controller:
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
$('input[type="text"]').each(function() {
console.log(this.value)
});
});
},
I need a generic way to check if the input field is empty, because I want to include this nice UI effect: http://codepen.io/aaronbarker/pen/tIprm
Update
I tried to implement this in a View, but now I get always the values from the record clicked before and not from the current clicked element:
View
Docket.OrganizationCustomersView = Ember.View.extend({
didInsertElement: function() {
$('input[type="text"]').each(function() {
console.log(this.value)
});
}.observes('controller.editedRecordID')
});
Controller
Docket.OrganizationCustomersController = Ember.ArrayController.extend({
/* ... */
isEditing: false,
editedRecordID: null,
actions: {
/* ... */
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
});
},
/* ... */
});
Update 2
OK, I think I misunderstood some things.
At first, my expected console output should be:
1.
2.
3.
but is:
1.
3.
2.
Secondly: I can use any name, even foobar, for the observed method in my view. Why?
Controller
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
console.log('1.')
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
console.log('2.')
});
},
View
Docket.OrganizationCustomersView = Ember.View.extend({
foobar: function() {
console.log('3.')
$('input[type="text"]').each(function() {
console.log(this.value)
});
}.observes('controller.editedRecordID')
});
Update 3
I think I "figured it out" (but I don't know why):
Docket.OrganizationCustomersView = Ember.View.extend({
movePlaceholder: function() {
$('input[type="text"], textarea').bind("checkval",function() {
var $obj = $(this);
setTimeout(function(){
console.log($obj.val());
},0);
}.observes('controller.editedRecordID')
});
setTimeout(function(){ ... }, 0); does the trick. But why?!
You can convert use that jquery code in a component, this is the best way to create a reusable view, without putting ui logic in controllers, routers etc.
Template
<script type="text/x-handlebars" data-template-name="components/float-label">
<div class="field--wrapper">
<label >{{title}}</label>
{{input type="text" placeholder=placeholder value=value}}
</div>
</script>
FloatLabelComponent
App.FloatLabelComponent = Ember.Component.extend({
onClass: 'on',
showClass: 'show',
checkval: function() {
var label = this.label();
if(this.value !== ""){
label.addClass(this.showClass);
} else {
label.removeClass(this.showClass);
}
},
label: function() {
return this.$('input').prev("label");
},
keyUp: function() {
this.checkval();
},
focusIn: function() {
this.label().addClass(this.onClass);
},
focusOut: function() {
this.label().removeClass(this.onClass);
}
});
Give a look in that jsbin http://emberjs.jsbin.com/ILuveKIv/3/edit

Creating a clean Knockout binding without business logic (Popover - Radio Buttons)

I'm looking for a clean way to implement the following. Let's assume I have 5 buttons:
(Dog) - (Cat) - (Fish) - (Bird) - (Other)
In my ViewModel these buttons are represented as being a classification. So when I click the Dog button, the observable in my ViewModel should be set to Dog (this would be done through the click binding on each button).
Also, I want to show a specific style when one of the buttons is toggled (done by the css binding on the button):
(Dog) - (Cat) - (Fish) - (Bird) - (Other)
So for now this looks like it should turn my buttons into a radio button group. Now besides that, when I click the "Other" button I also want to show a little popover (bootstrap) in which the user can specifiy a custom value, like "Lion". Clicking an other button would close the popover.
Now on each button I could add a binding similar to this one:
{ css: { 'toggled': classficationMatches('Dog') },
popover: { action: 'close', id: 'mypopover' },
click: { click:setClassification.bind($data, 'Dog') }
But this feels dirty. I would prefer building a custom handler and use it like this:
{ toggleClassification: { type: 'Dog', popover: 'mypopover' } }
Now it would be up to the ViewModel to decided if the popover should be visible or not and the binding would contain all the logic of adding the css binding, the click and the popover bindings to the buttons.
I started trying some things with custom bindings but this code looks even worse:
ko.bindingHandlers["toggleClassification"] = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var classification = $(element).attr('data-type');
var selector = ko.utils.unwrapObservable(valueAccessor());
var pickerViewModel = new SimpleFilterSpecializationPickerViewModel(element, selector);
// Whenever we click the button, show the popover.
ko.utils.registerEventHandler(element, "click", function () {
// Hide the popup if it was visible.
if (pickerViewModel.isVisible()) {
pickerViewModel.hide();
}
else {
var requireInit = !pickerViewModel.loaded();
// Show the popover.
pickerViewModel.show();
// The first time we need to bind the popover to the view model.
if (requireInit) {
ko.applyBindings(viewModel, pickerViewModel.getPopoverElement());
// If the classification changes, we might not want to show the popover.
viewModel.isClassification(classification).subscribe(function () {
if (!viewModel.isClassification(classification)()) {
pickerViewModel.hide();
}
});
}
}
$('button[data-popoverclose]').click(function () {
$(element).popover('hide');
});
});
}
};
ko.bindingHandlers["toggleClassification"] = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
// Store the current value on click.
$(element).click(function () {
var observable = valueAccessor();
viewModel.setClassification(observable);
});
// Update the css of the button.
ko.applyBindingsToNode(element, { css: { 'active': viewModel.isClassification(valueAccessor()) } }, viewModel);
}
};
Anyone have some tips on how I could clean up my bindings so most of the 'logic' can be done in the ViewModel?
You can add this "dirty" binding programmatically with
ko.applyBindingsToNode(Element element, Object bindings, Object viewModel)
e.g.
HTML
data-bind="toggleClassification: { type: 'Dog', popover: 'mypopover' }"
JS
ko.bindingHandlers["toggleClassification"] =
{
init: function (element, valueAccessor, allBindings, viewModel, bindingContext)
{
var type = valueAccessor().type; // 'Dog'
var popover = valueAccessor().popover; // 'mypopover'
var binding = {
css: { 'toggled': classficationMatches(type) },
popover: { action: 'close', id: popover },
click: { click:setClassification.bind(bindingContext.$data, type) }
};
ko.applyBindingsToNode(element, binding , viewModel);
}
};

Knockout register inserted element with other function

I am using Knockout to display some tags for an array of images. each tag will have a popup that gives more information about the tag. The elements is registered with popup class the following way:
function RegisterCharacterPopups() {
$('[data-characterid]').each(function() {
var cId = $(this).data('characterid');
var placement = $(this).data('position');
if (placement == null || placement == undefined) {
placement = "top-center";
}
$(this).PopUp({
url: "/Ajax/CharacterPop/" + cId,
position: placement,
});
});
}
And i have added this to my constructor of the view model that contains the tags:
// Hook on to update of Tags
ko.computed(() => {
var test = this.Tags();
RegisterCharacterPopups();
console.log("Tags updated");
});
I can see the methods is executed, but the tags do not register with the popup. if it force the Tags to update again, will it work though!
I think the problem is that this method in executed the first time, before the elements are in the html.
How can I fix this, so it will wait for the elements to be inserted before it executes it?
Solution: a custom binding
ko.bindingHandlers['tagpop'] = {
init: function (element, valueAccessor, allBindings, vm, context) {
var data = valueAccessor();
$(element).PopUp({
url: "/Ajax/CharacterPop/" + data.id,
position: data.placement,
});
},
update: function (element, valueAccessor) {
}
};

Scope problems when using setTimeout in backbone.js

I am trying to toggle a state variable in my Backbone collection ("Posts") after a certain period of time (called from a view), and am trying to use setTimeout. However, I think I am screwing up my scope as my toggle function is not working (it is getting called, but it is not changing properly).
If I use
setTimeout(this.model.collection.toggleReadyToPreloadNewPost, 1000);
, the code does not work, while if I use
this.model.collection.toggleReadyToPreloadNewPost();
it toggles it correctly. I was wondering how I can solve this?
Backbone View
//ensures namespace is not already taken yet
wedding.views = wedding.views || {};
//each PostView corresponds to a single post container
//which contains the user name, user comment, and a photo
wedding.views.PostView = Backbone.View.extend({
tagName: "li",
template: "#item-template",
className: "hideInitially post",
initialize: function() {
_.bindAll(this);
this.template = _.template($(this.template).html());
},
render: function() {
this.preload();
return this;
},
//preloads an image and only after it is done, then append
preload: function() {
var image = new Image();
image.src = this.model.get("src");
this.model.incrementTimesSeen();
//hides the element initially, waits for it to finish preloading, then slides it down
//updates lastSeen only after the image is displayed
image.onload = $.proxy(function() {
var html = this.template( {model: this.model.toJSON()} );
this.model.setLastSeen();
//shows the image by sliding down; once done, remove the hideInitially class
this.$el.hide().append(html).slideDown();
this.$el.removeClass("hideInitially");
setTimeout(this.model.collection.toggleReadyToPreloadNewPost, 1000);
}, this);
}
});
Backbone Collection
//check if namespace is already occupied
wedding.collections = wedding.collections || {};
wedding.collections.Posts = Backbone.Collection.extend({
model: wedding.models.Post,
initialize: function() {
this.readyToPreloadNewPost = 1;
},
//toggles "readyToPreloadNewPost" between 1 and 0
toggleReadyToPreloadNewPost: function() {
this.readyToPreloadNewPost = this.readyToPreloadNewPost ? 0 : 1;
}
});
When you do this:
setTimeout(this.model.collection.toggleReadyToPreloadNewPost, 1000);
You're just handing setTimeout the plain unbound toggleReadyToPreloadNewPost function and setTimeout will call it as a simple function. The result is that this will be window inside toggleReadyToPreloadNewPost when setTimeout calls the function.
You can get the right this by wrapping your method call in an anonymous function:
var _this = this;
setTimeout(function() {
_this.model.collection.toggleReadyToPreloadNewPost();
}, 1000);
You can also use _.bind:
setTimeout(
_(this.model.collection.toggleReadyToPreloadNewPost).bind(this.model.collection),
1000
);
You could also use _.bindAll inside the collection's initialize to always bind that method to the appropriate this:
wedding.collections.Posts = Backbone.Collection.extend({
initialize: function() {
_.bindAll(this, 'toggleReadyToPreloadNewPost');
//...
}
And then your original
setTimeout(this.model.collection.toggleReadyToPreloadNewPost, 1000);
should do the right thing. You'd only want to go this route if you always wanted toggleReadyToPreloadNewPost to be bound, I'd probably go with the first one instead.

Categories

Resources