Marionette Nested CompositeViews and Change Events Either not firing or happening in reverse - marionette

tl;dr
http://jsfiddle.net/fRTBU/2/
Have a Marionette App that I am showing a list of Baseball Cities.
window.teams = new MyApp.Models.TeamCollection([
{ League : "AL", City : "Boston" , TeamId : 1 },
{ League : "AL", City : "Chicago" , TeamId : 2 },
{ League : "NL", City : "Chicago" , TeamId : 3 }
]);
I want to have a list of the cities broken down by league.
something like :
<table>
<tr>
<td>
<h2>AL</h2>
<ul>
<li>Boston 1 </li>
<li>Chicago 2</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2>NL</h2>
<ul>
<li>Chicago 3 </li>
</ul>
</td>
</tr>
</table>
I can get that to render.
But when I try to add a new team (For expansion :P)
window.teams.push(new MyApp.Models.Team({ League : "NL",
City : "Washington",
TeamId : 4 }));
The collection change events don't fire ( in the JSFiddle )
http://jsfiddle.net/fRTBU/2/
collectionEvents: {
"reset": "myFunc" ,
"change" : "myFunc",
"add" : "myFunc"
},
myFunc: function () {
Logger('myFunc');
var grid = window.teams.groupBy(function (row) {
return row.get("League");
});
this.collection = new Backbone.Collection(_.toArray(grid));
}
or in my app (with more logic surrounding it )
ReferenceError: TeamId is not defined
main app structure
MyApp.Models.Team = Backbone.Model.extend({
idAttribute: 'TeamId'
});
MyApp.Models.TeamCollection = Backbone.Collection.extend({
model: MyApp.Models.Team
});
MyApp.Views.ListItemView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template : '#row-template'
});
MyApp.Views.ListCompositeView = Backbone.Marionette.CompositeView.extend({
itemView: MyApp.Views.ListItemView,
template: "#list-template",
itemViewContainer: "ul",
initialize: function () {
this.collection = new Backbone.Collection(
_.toArray(this.model.attributes));
},
onRender: function () {
$(this.$el).find("h2.ListHead")
.html(this.collection.models[0].get('League'));
}
});
MyApp.Views.TableView = Backbone.Marionette.CompositeView.extend({
itemView: MyApp.Views.ListCompositeView,
tagName: "table",
itemViewContainer: "tbody",
template: "#table-template",
initialize : function () {
this.myFunc();
},
collectionEvents: {
"reset": "myFunc" ,
"change" : "myFunc",
"add" : "myFunc"
},
myFunc: function () {
Logger('myFunc');
var grid = window.teams.groupBy(function (row) {
return row.get("League");
});
this.collection = new Backbone.Collection(_.toArray(grid));
}
});
When I say they are happening in reverse it appears as though the MyApp.Views.ListItemView gets a collection and MyApp.Views.ListCompositeView gets a model.
JsFiddle Link Again
http://jsfiddle.net/fRTBU/2/

Okay, so there are a few issues here. The reason your events aren't firing in the TableView is because you break the event bindings to the window.teams collection when you replace its collection with this.collection = new Backbone.Collection(_.toArray(grid)); -- at that point its collection is no longer window.teams.
Another issue is that you're trying to display the data as if it were modeled like:
Leagues
|______League
| |______Team
| |______Team
|
|______League
|______Team
|______Team
|______Team
... but your data is coming in structured like:
Teams
|______Team
|______Team
|______Team
One solution would be to have a TeamCollection that accepts your data in the { League : "AL", City : "Boston" , TeamId : 1 } format, and to bind change events to a function that updates a LeagueTeamsCollection per league, and use that collection as the basis for 2 view classes: a CompositeView and an ItemView.

Related

my code does not safe different pages in my db

js:
$('#sortable').sortable({
stop: function(event, ui) {
saveContentOrder();
}
});
function saveContentOrder()
{
const idsInOrder = $("#sortable").sortable("toArray");
var data = {
idsInOrder:idsInOrder
};
$.ajax({
type: "POST",
url: "/xxx/yyy/zzz",
data: data
}).done( function() {
}).fail(function() {
});
}
index.blade.php:
<tbody id="sortable" >
#foreach ($references as $index => $reference)
<tr id="reference_id_{{$reference->id}}">
<td width="65%">
<a href="{{ route('admin.reference.edit', $reference->id ) }}"><b>{{ $reference->title }}</b>
</a><br>
</td>
<td>
#if(!count($reference->images))<span style="color:#ff0000;font-weight:700;">0</span>#else{{ count($reference->images) }}#endif
</td>
<td>
{{ $reference->priority }}
my webroute:
Route::post('/xxx/yyy/zzz', 'AdminReferenceController#reorder');
my Controller:
public function reorder(Request $request)
{
$order = $request->get('idsInOrder',[]);
if (is_array($order))
{
foreach($order as $position => $idName)
{
$id = str_replace("reference_id_","",$idName);
$gesamt = Reference::all()->count();
$c = \App\Reference::find($id);
if($c)
{
$c->priority = $gesamt-$position;
$c->save();
}
}
}
when i am on my first page it saves the position and priority change that i drag and drop.but when i go to the second page for example and drag and drop the order it gives the same priority as in page 1. which means it displays thinks first that should be 20th or 30th. i basically want it to be on the right order all the time. i have a show 10, show 30, and show 100. when i for example get to the show 30 and i have no pages since i dont have so many entries right now it works without issues. but as soon as i go to show 10 and got 3 pages the priority gets mixed up. how can i fix this
this is js File:
$('#sortable').sortable({
update: function(event, ui) {
saveContentOrder();
}
});
function saveContentOrder()
{
const idsInOrder = $(this).sortable("serialize");
var data = {
idsInOrder:idsInOrder
};
$.ajax({
type: "POST",
url: "/xxx/yyy/zzz",
data: data
}).done( function() {
}).fail(function() {
});
}
this is controller file
public function reorder(Request $oRequest)
{
//$oRequest->idsInOrder get all orders positiion ids.affter drag
data like,
//orders[]=6&orders[]=5&orders[]=3&orders[]=4&orders[]=2&orders[]=1
parse_str($oRequest->idsInOrder);
$nCount = 1;
foreach($orders as $order)
{
// update database order
$nCount++;
}
return Response::json(['success' => true]);
}

Vue.js Component combined with Laravel Form Validation (i.e. passing an initial value for data)

After much Googling and finding the Vue.js forum down, I am ready to give up.
I'm creating a Postcode Lookup component, and everything was working well until I tried to combine it with Laravel's form validation - particularly when there's an error, and the form re-fills the old values.
Hopefully I cover everything here. I have a form input partial that I use which generates every form input. It also uses Laravel's old(...) value if present.
The issue is because there's a default value (in this case for postcode and address) of an empty string, this overrides the value attribute of Postcode input, and the content of the Address textarea.
In made up land, the ideal would be:
data : function() {
return {
postcode : old('postcode'),
address : old('address'),
addresses : [],
hasResponse : false,
selectedAddress : ''
};
},
So that's what I'm trying to replicate.
I can probably replace validation with Ajax validation, but my form partial changes the appearance of fields with an error slightly, so this would be messy
From my understanding:
I can't set an initial data value, as this will override the input value.
I can set a prop, but this is immutable
Any help I can find suggests 'using a computed property which determines its value from the prop' but if you literally do that, it doesn't update.
Here's what I have so far:
<so-postcode-lookup initial-postcode="{{ old('postcode') }}" initial-address="{{ old('address') }}"></so-postcode-lookup>
/**
* Allow user to select an address from those found in the postcode database
*/
Vue.component('so-postcode-lookup', {
name : 'so-postcode-lookup',
template : '#so-postcode-lookup-template',
props : ['initialPostcode', 'initialAddress'],
data : function() {
return {
postcode : '',
address : '',
addresses : [],
hasResponse : false,
selectedAddress : ''
};
},
computed : {
currentAddress : function() {
if (this.address !== '') {
return this.address;
} else {
return this.initialAddress;
}
},
currentPostcode : function() {
if (this.postcode !== '') {
return this.postcode;
} else {
return this.initialPostcode;
}
},
hasAddresses : function() {
return this.addresses.length;
},
isValidPostcode : function() {
return this.postcode !== '' && this.postcode.length > 4;
},
isInvalidPostcode : function() {
return !this.isValidPostcode;
}
},
methods : {
fetchAddresses : function() {
var resource = this.$resource(lang.ajax.apiPath + '/postcode-lookup{/postcode}');
var $vm = this;
var element = event.currentTarget;
// Fetch addresses from API
resource.get({ postcode : this.postcode }).then(function(response) {
response = response.body;
if (response.status == 'success') {
// Update addresses property, allowing select to be displayed
$vm.addresses = response.data;
} else {
$vm.addresses = [];
}
this.hasResponse = true;
});
},
setAddress : function() {
this.address = this.selectedAddress;
}
}
});
<template id="so-postcode-lookup-template">
<div class="row">
#include('partials.input', [
'label' => trans('register.form.postcode'),
'sub_type' => 'postcode',
'input_id' => 'postcode',
'autocorrect' => false,
'input_attributes' => 'v-model="currentPostcode"',
'suffix_button' => true,
'suffix_button_reactive' => trans('register.form.postcode_button_reactive'),
'suffix_text' => trans('register.form.postcode_button'),
'required' => true,
'columns' => 'col-med-50',
'wrapper' => 'postcode-wrapper'
])
<div class="col-med-50 form__item" v-show="hasResponse">
<label for="address-selector" class="form__label" v-show="hasAddresses">{{ trans('forms.select_address') }}</label>
<select id="address-selector" class="form__select" v-show="hasAddresses" v-model="selectedAddress" #change="setAddress">
<template v-for="address in addresses">
<option :value="address.value">#{{ address.text }}</option>
</template>
</select>
<so-alert type="error" allow-close="false" v-show="!hasAddresses">{{ trans('forms.no_addresses') }}</so-alert>
</div>
#include('partials.input', [
'label' => trans('register.form.address'),
'input_id' => 'address',
'type' => 'textarea',
'input_attributes' => 'v-model="currentAddress"',
'required' => true
])
</div>
</template>
If I try this, and set the model of the inputs to currentPostcode and currentAddress respectively, I seem to get an infinite loop.
I think I'm overthinking this somehow.
You can't bind directly to a prop but you can set an initial value using the prop and then bind to that, which is the way to go if you need a two way binding:
Vue.component('my-input', {
props: {
'init-postcode': {
default: ""
}
},
created() {
// copy postcode to data
this.postcode = this.initPostcode;
},
data() {
return {
postcode: ""
}
},
template: '<span><input type="text" v-model="postcode"> {{ postcode }}</span>'
});
Then just do:
<div id="app">
<my-input init-postcode="{{ old('postcode') }}"></my-input>
</div>
Here's the fiddle: https://jsfiddle.net/vL5nw95x/
If you are just trying to set the initial values, but don't need a two way binding, then you can reference the prop directly - as you won't be applying any changes - using v-bind:value:
Vue.component('my-input', {
props: {
'init-postcode': {
default: ""
}
},
template: '<span><input type="text" :value="initPostcode"> {{ postcode }}</span>'
});
And the markup:
Here's the fiddle: https://jsfiddle.net/pfdgq724/
Im working in a easy way to do that using laravel 5.4 controller to send the data directly
In Laravel view:
<input class="form-control" id="ciudad" name="ciudad" type="text" v-model="documento.ciudad" value="{{ old('ciudad', isset($documento->ciudad) ? $documento->ciudad : null) }}" >
in vue.js 2.0
data: {
documento: {
ciudad: $('#ciudad').val(),
},
},
In Laravel Controller
$documento = ReduJornada::where("id_documento",$id)->first();
return view('documentos.redujornada')->with(compact('documento'));

CanJS - Control Communication

I am learning CanJS now , so I want to try a very basic small demo. The demo is you will have different types of mobile recharge plans which displayed on the top (Radio buttons) and by choosing each plan the corresponding price options will be displayed in a table at the bottom.
For this demo I create two Model , 2 Control and 2 Template files , my question is how can two control communicate with each other? What is the standard way?
For now I am directly calling the control method through its instance , but I am not sure if it is right way to do. Also please explain Can.Route.
Output
http://jsfiddle.net/sabhab1/2mxfT/10/
Data
var CATEGORIES = [{id: 1 , name: "2G Internet Recharge"},
{id: 2 , name: "3G Internet Recharge"},
{id: 3 , name: "full talktime Recharge"},
{id: 4 , name: "Validity and talktime Recharge"},
{id: 5 , name: "National and international roaming"}];
var RECHARGEAMOUNTS =[{
id: 1 ,
values : [{amount: "Rs. 100" , benefit:"300 MB" ,validity:"30"},
{amount: "Rs. 200" , benefit:"1 GB" ,validity:"30"}]
},
{
id: 2 ,
values : [{amount: "Rs. 10" , benefit:"300 MB" ,validity:"30"},
{amount: "Rs. 99" , benefit:"100 GB" ,validity:"90"}]
},
{
id: 3 ,
values : [{amount: "Rs. 80" , benefit:"1 GB" ,validity:"50"},
{amount: "Rs. 99" , benefit:"100 GB" ,validity:"50"}]
},
{
id: 4 ,
values : [{amount: "Rs. 55" , benefit:"30 MB" ,validity:"10"},
{amount: "Rs. 200" , benefit:"1 GB" ,validity:"30"},
{amount: "Rs. 99" , benefit:"100 GB" ,validity:"90"}]
},
{
id: 5 ,
values : [{amount: "Rs. 880" , benefit:"100 MB" ,validity:"90"},
{amount: "Rs. 550" , benefit:"2 GB" ,validity:"30"},
{amount: "Rs. 1000" , benefit:"4 GB" ,validity:"90"},
{amount: "Rs. 1550" , benefit:"10 GB" ,validity:"90"}]
}
];
Model
//Model Category
CategoryModel = can.Model({
findAll : function(){
return $.Deferred().resolve(CATEGORIES);
}
},{});
//Model Category
ReachargeAmountModel = can.Model({
findAll : function(){
return $.Deferred().resolve(RECHARGEAMOUNTS);
},
findOne : function(params){
return $.Deferred().resolve(RECHARGEAMOUNTS[(+params.id)-1]);
}
},{});
Control
**// Can Control
var CategoryControl = can.Control({
// called when a new Todos() is created
init: function (element, options) {
// get all todos and render them with
// a template in the element's html
var el = this.element;
CategoryModel.findAll({}, function (values) {
el.html(can.view('categoriesEJS', values))
});
this.options.rchAmtCtrl = new RechargeAmountControl("#rechnageAmountView");
},
'input click' : function( el, ev ) {
var id = el.data('category').attr('id');
console.log(id);
this.options.rchAmtCtrl.update(id);
}
});
// Can Control
var RechargeAmountControl = can.Control({
// called when a new Todos() is created
init: function (element, options) {
// get all todos and render them with
// a template in the element's html
this.update(1);//this.update(id,this.element);
},
update : function(id){
var el = this.element;
ReachargeAmountModel.findOne({id: id}, function( rechargeAmount ){
// print out the todo name
//console.log(rechargeAmount.values[id].attr('benefit'));
el.html(can.view('RechnageAmountEJS', rechargeAmount.values));
});
}
});**
View
<form id='categoriesView'></form>
</p>
<table id='rechnageAmountView'></table>
<script type='text/ejs' id='RechnageAmountEJS'>
<tr>
<th>Recharge Amount</th>
<th>Benefits</th>
<th>Validity(Days)</th>
</tr>
<% this.each(function( rechargeAmount ) { %>
<tr>
<td>
<%= rechargeAmount.attr( 'amount' ) %>
</td>
<td>
<%= rechargeAmount.attr( 'benefit' ) %>
</td>
<td>
<%= rechargeAmount.attr( 'validity' ) %>
</td>
</tr>
<% }) %>
</script>
<script type='text/ejs' id='categoriesEJS'>
<% this.each(function( category ) { %>
<input type="radio"
name="category"
<%= category.attr('id') == 1 ? 'checked' : '' %>
value=<%= category.attr( 'name' ) %>
<%= (el) -> el.data('category',category) %>>
<%= category.attr( 'name' ) %>
</input>
<% }) %>
</script>
Main Call
new CategoryControl("#categoriesView");
There are several ways for doing this.
1. Calling methods directly
This is what you are doing and isn't necessarily wrong. To make things a little more flexible you could pass the class or instance of RechargeAmountControl when initializing CategoryControl instead of using it directly.
2. DOM events
Here it goes a little more into event oriented architecture. If you generally want to notify other Controls you can just trigger any kind of event and make them listen to it. Something like this: http://jsfiddle.net/2mxfT/11/
' rechargeAmountUpdated': function(element, event, id){
var el = this.element;
console.log(arguments);
ReachargeAmountModel.findOne({id: id}, function( rechargeAmount ){
// print out the todo name
//console.log(rechargeAmount.values[id].attr('benefit'));
el.html(can.view('RechnageAmountEJS', rechargeAmount.values));
});
}
3. Observables
Another option is to use Observables to maintain shared state. This is a great way to focus on the data and let live binding do all the rest. To make things more flexible, the state object should be passed during Control initialization (see http://jsfiddle.net/2mxfT/12/):
var state = new can.Observe();
new RechargeAmountControl("#rechnageAmountView", {
state: state
});
new CategoryControl("#categoriesView", {
state: state
});
state.attr('rechargeId', 1);
And then you can just listen to attribute changes in the RechargeAmountControl like this:
'{state} rechargeId': function(Construct, event, id){}
This handler will be called whenever you update your state Observe.
And this is also where can.route comes in. Basically can.route is an Observe that saves its state in the location hash. In the above example like #!&rechargeId=1 (unless you initialize a specific route like can.route(':rechargeId')). If the location hash changes the Observe will be updated and vice versa.

BackboneJS and Model/Collection

Have json-data like this:
{ tournaments: [ {
tournament_id: "..."
tournament_name: "..."
events: [ {
event_id: ...
event_name: ....
param : [ {
param_a :
param_b : ..
subparan : [ {
sub_1: 1
sub_2 : 2...
So. I don't understand - how to it implement into BackBone Collection/Model style?
How to handle change sub_1? - Made Collection of Collection of Collection?
Simpliest way described in backbone tutorial:
var Events = Backbone.Collection.extend({
initialize: function(){
this.params = new Params()
}
})
var Tournaments = Backbone.Model.extend({
initialize: function(){
this.events = new Events()
}
})
var tournaments = new Tournaments()
You can continue nesting by you needs. When I was working on similar task I wrap each collection in model representing collection state and change itself in answer of collection events. This allows not to asking nested collections about its state having actual state in model.
var CollModel = Backbone.Model.extend({
defaults: {
state = ''//or list or dict or whatever
},
initialize: function(){
this.items = new Backbone.Collection();
this.items.on('all', this.setState, this)
},
setState: function(){
this.set(
'state',
this.items.reduce(function(state, item){
/*calculate state*/
}, '')
)
},
info: function(){
return this.get('state')
}
})
So you can nest collection-models with similar technic and read their state directly through instance.info() depends on how you calculate it. Your top model state will be updated from cascade updates of underneath models-collections.

In Backbone Collection, delete a model by link on itself

Im just trying to delete a model from a collection, with a link on itself.
I've attach the event to the "Eliminar button" but it seems Im losing the reference to the model element that contains it... and can't find it.. can you?:
(function ($) {
//Model
Pelicula = Backbone.Model.extend({
name: "nulo",
link: "#",
description:"nulo"
});
//Colection
Peliculas = Backbone.Collection.extend({
initialize: function (models, options) {
this.bind("add", options.view.addPeliculaLi);
this.bind("remove", options.view.delPeliculaLi);
}
});
//View
AppView = Backbone.View.extend({
el: $("body"),
initialize: function () {
this.peliculas = new Peliculas( null, { view: this });
//here I add a couple of models
this.peliculas.add([
{name: "Flying Dutchman", link:"#", description:"xxxxxxxxxxxx"},
{name: "Black Pearl", link: "#", description:"yyyyyyyyyyyyyy"}
])
},
events: {"click #add-movie":"addPelicula", "click .eliminar":"delPelicula"},
addPelicula: function () {
var pelicula_name = $("#movieName").val();
var pelicula_desc = $("#movieDesc").val();
var pelicula_model = new Pelicula({ name: pelicula_name },{ description: pelicula_desc });
this.peliculas.add( pelicula_model );
},
addPeliculaLi: function (model) {
var str= model.get('name').replace(/\s+/g, '');
elId = str.toLowerCase();
$("#movies-list").append("<li id="+ elId +"> " + model.get('name') + " <a class='eliminar' href='#'>Eliminar</a> </li>");
},
delPelicula: function (model) {
this.peliculas.remove();
console.log("now should be triggered the -delPeliculaLi- event bind in the collection")
},
delPeliculaLi: function (model) {
console.log(model.get('name'));
$("#movies-list").remove(elId);
}
});
var appview = new AppView;
})(jQuery);
And my html is:
<div id="addMovie">
<input id="movieName" type="text" value="Movie Name">
<input id="movieDesc" type="text" value="Movie Description">
<button id="add-movie">Add Movie</button>
</div>
<div id="lasMovies">
<ul id="movies-list"></ul>
</div>
There are several things in this code that won't work. Your major problem here is that you don't tell your collection which model to remove. So in your html you have to assign so unique id that later will identify your model.
// set cid as el id its unique in your collection and automatically generated by collection
addPeliculaLi: function (model) {
$("#movies-list").append("<li id="+ model.cid +"> <a href="+ model.get('link')+">" +
model.get('name') + "</a> <a class='eliminar' href='#'>Eliminar</a> </li>"
);
},
// fetch and delete the model by cid, the callback contains the jQuery delete event
delPelicula: function (event) {
var modelId = this.$(event.currentTarget).attr('id');
var model = this.peliculas.getByCid(modelId);
this.peliculas.remove(model);
// now the remove event should fire
},
// remove the li el fetched by id
delPeliculaLi: function (model) {
this.$('#' + model.cid).remove();
}
If there aren't other errors that I have overlooked your code should work now. This is just a quick fix. Maybe you should have a look at the todos example of Backbone to get some patterns how to structure your app.
http://documentcloud.github.com/backbone/examples/todos/index.html

Resources