I have a Marionette.CompositeView. I added the ability to remove an item from an item from the collection in the Composite's item view. The CompositeView displays a summary- which is nothing more than the count of items in it's collection.
What is the best way to update the CompositeView when an item is deleted:
app = new Marionette.Application()
app.addRegions
main: '#main'
app.module 'Views', (views, app)->
views.MyItemView = Marionette.ItemView.extend
template: '#my-view-template'
tagName: 'li'
events:
'click' : ()->
#model.collection.remove #model
views.MyCompositeView = Marionette.CompositeView.extend
itemView: views.MyItemView
template: '#my-composite-view-template'
itemViewContainer: 'ul'
initialize: ()->
#model = new Backbone.Model
count: #collection.length
#collection.on 'remove', ()->
console.log 'remove'
app.on 'start', ->
app.main.show new app.Views.MyCompositeView
collection: new Backbone.Collection [
new Backbone.Model( foo: 'bar')
new Backbone.Model( foo: 'bar')
new Backbone.Model( foo: 'bar')
]
$ ()->
app.start()
and here is the html
<div id="main">Hello world</div>
<script type='htm/text' id='my-view-template'>
I am dynamic <em><%= foo %></em>
</script>
<script type='html/text' id='my-composite-view-template'>
<div id='counter'><%= count %></div>
<ul></ul>
</script>
Collection and composite views will rerender child item views (and/or remove individual item views). In your case, you want to rerender the entire composite view when the collection is added to or subtracted from. For that purpose, bind the render method to the appropriate collection events in your view definition:
collectionEvents: {
"add": "render",
"remove": "render"
}
Related
I have this in my view:
<div class="already_voted" v-if="already_voted" >
<p>You already voted or your are not allowed to vote</p>
</div>
This is my method :
upvote: function(com_id) {
var comment_id = {
comment_id :com_id
}
this.$http.post('/blog/article/comment/upvote', comment_id).then(function(response){
upvote_total= response.data.upvote_value;
this.already_voted = response.data.already_voted;
this.$dispatch('child-msg', this.already_voted);
$('.upvote_class_' + com_id ).text(upvote_total);
$('.isDisabledUpvote_' + com_id).addClass('disabled');
$('.isDisabledDownvote_' + com_id).removeClass('disabled');
},function(response){
});
},
Im getting value on click and if its true it need to show this div.
Problem is that this div is showed only for first time when already_voted is true and thats it. Next time when its true nothing happend. Any suggestion?
It looks like you are mixing jQuery and Vue, which should be avoided unless you have a specific reason to do so. Instead you should bind attributes to data. As a basic version of what you are doing you could bind both the disabled attribute and the message to a voted flag:
Markup
<div id="app">
<div v-if="voted">
You have already voted!
</div>
<button v-bind:disabled="voted" #click="vote()">
Vote
</button>
<button v-bind:disabled="!voted" #click="removeVote()">
Un-Vote
</button>
</div>
View Model
new Vue({
el: '#app',
methods: {
vote(){
this.voted = true;
},
removeVote(){
this.voted = false;
}
},
data: {
voted: false
}
});
Here I'm simply binding the disabled attribute using v-bind to the voted flag to disabled the buttons and am using v-if to show a message if the voted flag is true.
Here's the JSFiddle: https://jsfiddle.net/05sbjqLL/
Also be aware that this inside an anonymous function refers to the anonymous function itself, so either assign this to something (var self = this) outside the function or use an arrow function if using ES6.
EDIT
I've updated the JSFiddle to show you how you might handle your situation based on you comments:
https://jsfiddle.net/umkvps5g/
Firstly, I've created a directive that will allow you to initiate your variable from your cookie:
Vue.directive('init', {
bind: function(el, binding, vnode) {
vnode.context[binding.arg] = binding.value;
}
})
This can now be used as:
<div v-init:voted="{{ $request->cookie('voted') }}"></div>
I simply disabled the button to show you how to bind attributes to data, there's loads more that can be done, for example showing the message after a user clicks the button, I've just added a click counter and bound thev-if to that instead, so the message doesn't show until a user clicks the button:
<div v-if="vote_attempts">
You have already voted!
</div>
Then in vote() method:
vote() {
this.voted = true;
this.vote_attempts++;
},
Then data:
data: {
voted: false,
vote_attempts: 0
}
In my web application I have (let's say) 2 tabs, both are custom knockout components. Their visibility is controlled by the following syntax:
<div id="page" data-bind="component: { name: currentTab }"></div>
where currentTab is an observable with the name of the current tab.
In both tabs I have visualisations with D3.js, using custom bindings. The problem is that these custom bindings are reinitialised after opening a tab. Is there a way to only load them once so that they don't need to be redrawn?
Note that the component's viewmodels aren't reinitialised, as they are created with the { instance: new viewModel() } trick:
define(['knockout', 'text!./tab-one.html', 'jquery'], function(ko, template, $) {
function ViewModel() {
var self = this;
};
return {
viewModel: { instance: new ViewModel() },
template: template
};
});
You may need to change slightly your approach:
<div id="page">
<div id="tab-1" data-bind="visible: (currentTab() == 'drawing-1'), component:{name:'drawing-1'}"></div>
<div id="tab-2" data-bind="visible: (currentTab() == 'drawing-2'), component:{name:'drawing-2'}"></div>
</div>
As alternative, you can add some more flexibility to your components if you provide a viewmodel, for example to encapsulate the logic of the visibility inside the component itself, and moreover - if you can also eventually skip the first redraw at component initialization:
This is just an example, but you can see the idea:
ko.components.register('drawing', {
viewModel: function(params) {
// Behaviors
this.active1 = params.currentTab == 'drawing-1';
this.active2 = params.currentTab == 'drawing-2';
this.draw = ko.computed(function() {
if (!ko.computedContext.isInitial()) {
// do some changes to the drawing
}
});
},
template:
'<div data-bind="visible: active1">'+
// drawing 1
'</div>'+
'<div data-bind="visible: active2">'+
// drawing 2
'</div>'
});
I created a dropdownlist using jqwidgets and i got a form using velocity template. In the form action i want to get the selected value of my dropdownlist. i need the value in my controller page. How can i get the selected value of the dropdownlist inside velocity template variable? I am passing the item inside $submitUrl.setParameter("filePath", "item"). but if I print the value inside controller then I am getting only item. not the selected value of the dropdownlist. Any suggession?
<script type="text/javascript">
$(document).ready(function ()
{
// Create the countryjqxWidget DropDownList
$("#countryjqxWidget").jqxDropDownList({source: countryList, selectedIndex: 0, width: '200', height: '25', theme: 'ui-redmond'});
$('#countryjqxWidget').on('change', function (event)
{
var args = event.args;
if (args) {
// index represents the item's index.
var index = args.index;
var item = args.item;
// get item's label and value.
var label = item.label;
var value = item.value;
alert(value);
var item = $("#jqxDropDownList").jqxDropDownList('getSelectedItem');
}
});
});
</script>
<form id="form" class="form-horizontal" action="$submitUrl" method="post" >
#lr_btn("submit-btn", "Submit", "Submitting...")
</form>
#set( $submitUrl = $renderResponse.createActionURL() )
$submitUrl.setParameter("submit", "upload")
$submitUrl.setParameter("filePath", "item")
Have you tried by adding the "countryjqxWidget" within your Form and setting its "name" attribute. As far as I know, if you set the "name" attribute, the selected value will be passed automatically on Form submit. It does not matter that the jQWidgets DropDownList is created from DIV tag, You can still set its "name" attribute.
I'm still learning Backbone and Marionette so forgive me if this is trivial.
I have a Backbone.Marionette.CompositeView that I iterate over to render a collection of Backbone.Marionette.ItemView.
My ItemView renders with a className: 'column'.
Is there a way to add the iterator as part of the className for each ItemView?
What I'm trying to accomplish is that the elements render as following:
<div class="column column-1"></div>
<div class="column column-2"></div>
<div class="column column-3"></div>
...
I couldn't find a suitable solution in the docs nor other questions here.
Thanks!
Essentially what you need to know is the index of the itemView model in your collection. Something like this will work:
// Create an itemView
var itemView = Backbone.Marionette.ItemView.extend({
template: "#item-template",
onRender: function () {
this.$el.addClass('class-nr-' + this.options.itemIndex);
}
});
// Create a collectionView
var colView = Backbone.Marionette.CollectionView.extend({
collection: colInstance,
itemView: itemView,
itemViewOptions: function (model, index) {
return {
itemIndex: index
};
}
});
See this fiddle: http://jsfiddle.net/Cardiff/VTkB2/2/
Documentation: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#collectionviews-itemviewoptions
Is there a way to use AJAX on a DropDownList changed event to dynamically modify a partial view on a page?
My main page has a DropDownList (DropDownListFor) and a partial view which ONLY contains a list of "items". The items shown in this partial view are dependent upon the item selected in the DropDownList. There's a 1 to many relationship between the DropDownList item and the items in the partial view. So, when the user changes the value of the DropDownList, the content in the partial view will dynamically change to reflect the item selected in the DropDownList.
Here's my DropDownList:
<div data-role="fieldcontain">
Choose Capsule:<br />
#Html.DropDownListFor(x => x.CapsuleFK, new SelectList(Model.Capsules, "pk", "name", "pk"), new { id = "ddlCapsules" })
<br />
</div>
Here's my Partial View declaration on the same page:
<div data-role="fieldcontain">
#Html.Partial("_FillerPartial", Model.Fillers)
</div>
I'm not very familiar with Ajax, but looking at other examples, here's what I have for my Ajax:
$(document).ready(function () {
$('#ddlCapsules').change(function () {
// make ajax call to modify the filler list partial view
var selection = $('#ddlCapsules').val();
var dataToSend = { cappk: selection };
$.ajax({
url: 'Process/GetFillersByCapsule',
data: { cappk: dataToSend },
success: function (data) {
alert("server returned: " + data);
}
});
});
});
And finally, here's a screenshot of what's going on. By changing the "Choose Capsule" drop down list, I want the Filler list to update dynamically:
You can load the drop down list as a partial view from the controller using ajax.
The controller code:
[HttpGet]
public virtual ActionResult GetFillersByCapsule(string cappk)
{
var model = //Method to get capsules by pk, this returns a ViewModel that is used to render the filtered list.
return PartialView("PartialViewName", model);
}
The main view html:
<div id="filteredList">
</div >
The partial view
#model IEnumerable<MyCapsuleModel>
foreach (var x in Model)
{
//Render the appropriate filtered list html.
}
And you can load the filtered list using ajax:
$('#ddlCapsules').change(function () {
// make ajax call to modify the filler list partial view
var selection = $('#ddlCapsules').val();
var dataToSend = { cappk: selection };
$.ajax({
url: 'Process/GetFillersByCapsule',
data: { cappk: dataToSend },
success: function (data) {
$("#filteredList").empty();
$("#filteredList").html(data);
}
});
});
Hope this helps.
You can't update the partial, per se, because the partial will never be rendered again without a page reload. Once you receive the HTML, ASP is done, you're on your own at that point.
What you can do, of course, is switch out the content of a particular div or whatever using JavaScript. Your example in particular screams Knockout, so that's what I would recommend using.
Change your HTML to add a data-bind to your containing div:
<div data-role="fieldcontain" data-bind="foreach: filler">
<button data-bind="text: name"></button>
</div>
And your DropDownList:
#Html.DropDownListFor(x => x.CapsuleFK, new SelectList(Model.Capsules, "pk", "name", "pk"), new { id = "ddlCapsules", data_bind = "event: { change: updateFillers }" })
Then, some JavaScript:
var FillersViewModel = function () {
var self = this;
self.fillers = ko.observableArray([]);
self.updateFillers = function () {
var selection = $('#ddlCapsules').val();
var dataToSend = { cappk: selection };
$.ajax({
url: 'Process/GetFillersByCapsule',
data: { cappk: dataToSend },
success: function (data) {
self.fillers(data.fillers) // where `fillers` is an array
}
});
}
}
var viewModel = new FillersViewModel();
ko.applyBindings(viewModel);
This is a very simplistic example, and you'll need to do some more work to make it do everything you need it to do in your scenario, but the general idea is that every time the dropdown list is changed, Knockout will call your updateFillers method, which will execute the AJAX and put new data into the fillers observable array. Knockout automatically tracks changes to this array (hence the "observable" part), so an update is automatically triggered to any part of your page that relies on it. In this scenario, that's your div containing the buttons. The foreach binding will repeat the HTML inside for each member of the array. I've used a simple button element here just to illustrate, but you would include the full HTML required to create your particular button like interface. The text binding will drop the content of name in between the opening and closing tag. Refer to: http://knockoutjs.com/documentation/introduction.html for all the binding options you have.
There's much more you could do with this. You could implement templates instead of hard-coding your HTML to be repeated in the foreach. And, you can use your partial view to control the HTML for this template. The important part is that Knockout takes the pain out of generating all this repeating HTML for you, which is why I recommend using it.
Hope that's enough to get you started.