Backbone rendering collection in other collection element - javascript

I have a structure that I need to render. It has linear type:
{districts : [
{
name : "D17043"
},
{
name : "D91832"
},
{
name : "D32435"
}
],
buildings : [
{
name : "B14745",
district: "D17043",
address: "1st str"
},
{
name : "B14746",
district : "D91832",
address : "1st str"
},
{
name : "B14747",
district : "D17043",
address : "1st str"
}
]
}
It is simplified to look a bit better. Districts have buildings attached to them. Possibly, the structure could be nested, but I thought that it would make additional troubles, and, as this "tree" is not structured I made simple object.
The question is - how it can be properly displayed with backbone?
Right now it looks something like this (some details are left behind the scenes):
App.Collections.Districts = Backbone.Collection.extend({
model : App.Models.District
});
var districtsCollection = new App.Collections.Districts(data.districts);
var districtsView = new App.Views.Districts({collection : districtsCollection});
then somewhere in district collection view we create a loop that make views of a single district:
var districtView = new App.Views.District({model : district});
and inside view of a single district i call another collection in render method:
App.Views.District = Backbone.View.extend({
tagName : 'div',
className : 'districtListElement',
render : function () {
this.$el.html(this.model.get('name'));
var buildingsCollection = new App.Collections.Buildings(allData.buildings);
buildingsCollection.each(function(item){
if (item.get('district') == this.model.get('name')) {
var buildingView = new App.Views.Building({model : item});
this.$el.append(buildingView.render().el);
}
}, this);
return this;
}
});
This thing works but isn't it a bit creepy? Maybe there is a proper way to do it? Especially if I have other instances to be inside buildings.
Thank you.

Related

Add different controls in different rows in UI5 table

I have table sap.ui.table.Table ans I have a model in which some records have links and some doesn't. I want to render the link in the sap.m.Link component in the column and when the link is not available in the record, it should render "Link is not provided." in the sap.m.Text in the column.
As the sap.ui.table.Column has the template aggregation which does not support binding aggregation as it is only 0 or 1 controls supported. And the formatter is also applicable here. Is there any way that the content of the column can be changed runtime according to the module data?
My module data is :
var data = [{
id : 1,
link : 'abc.com'
},
{
id : 2
},
{
id : 3,
link : 'pqr.com'
}]
I am providing the code:
var link = new sap.m.Link({text : "{link}"});
var noLink = new sap.m.Text({text : "Link is not provided."});
var idColumn = new sap.ui.table.Column({
label : [new sap.m.Label({text : "ID"})],
template : [new sap.m.Text({text : "{id}"})]
});
var linkColumn = new sap.ui.table.Column({
label : [new sap.m.Label({text : "Link"})],
template : [??????]
});
var table = new sap.ui.table.Table({
columns : [idColumn, linkColumn]
});
var model = new sap.ui.model.json.JSONModel();
model.setData({items : data});
table.setModel(model);
table.bindRows("/items");
I want to add the link and noLink in the column likColumn runtime according to the data in the module. How can I achieve this?
The display content of each column can be changed using formatter
e.g:
new sap.m.Link({
width: "20em",
//editable: false,
//text: "{items>link}"
text: {
path: "items>link",
formatter: function(link){
if (link === undefined) return "Link is not provided"
return link;
}
}
});
...
oTable.addEventDelegate({
onAfterRendering: function(){
$('#idTable a:contains("Link is not provided")').removeClass("sapMLnk");
}
}, oTable);
UPDATE: This is a jsbin with the full example of what you need:
UPDATED example

Update array order in Vue.js after DOM change

I have a basic Vue.js object:
var playlist = new Vue({
el : '#playlist',
data : {
entries : [
{ title : 'Oh No' },
{ title : 'Let it Out' },
{ title : 'That\'s Right' },
{ title : 'Jump on Stage' },
{ title : 'This is the Remix' }
]
}
});
HTML:
<div id="playlist">
<div v-for="entry in entries">
{{ entry.title }}
</div>
</div>
I also am using a drag and drop library (dragula) to allow users to rearrange the #playlist div.
However, after a user rearranges the playlist using dragula, this change is not reflected in Vue's playlist.entries, only in the DOM.
I have hooked into dragula events to determine the starting index and ending index of the moved element. What is the correct way to go about updating the Vue object to reflect the new order?
Fiddle: https://jsfiddle.net/cxx77kco/5/
Vue's v-for does not track modifications to the DOM elements it creates. So, you need to update the model when dragula notifies you of the change. Here's a working fiddle: https://jsfiddle.net/hsnvweov/
var playlist = new Vue({
el : '#playlist',
data : {
entries : [
{ title : 'Oh No' },
{ title : 'Let it Out' },
{ title : 'That\'s Right' },
{ title : 'Jump on Stage' },
{ title : 'This is the Remix' }
]
},
ready: function() {
var self = this;
var from = null;
var drake = dragula([document.querySelector('#playlist')]);
drake.on('drag', function(element, source) {
var index = [].indexOf.call(element.parentNode.children, element);
console.log('drag from', index, element, source);
from = index;
})
drake.on('drop', function(element, target, source, sibling) {
var index = [].indexOf.call(element.parentNode.children, element)
console.log('drop to', index, element, target, source, sibling);
self.entries.splice(index, 0, self.entries.splice(from, 1)[0]);
console.log('Vue thinks order is:', playlist.entries.map(e => e.title ).join(', ')
);
})
}
});
I created a Vue directive that does exactly this job.
It works exactly as v-for directive and add drag-and-drop capability in sync with underlying viewmodel array:
Syntaxe:
<div v-dragable-for="element in list">{{element.name}}</div>
Example: fiddle1, fiddle2
Github repository: Vue.Dragable.For

Performance issue with JSON and auto Complete [closed]

Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I am trying to write an app that will work online and offline using technologies such as application cache and local storage. I am using jQuery mobile and an jqm autocomplete solution which can be found here http://andymatthews.net/code/autocomplete/
The idea is that if the app is on-line then I will be calling a remotely accessible function via ajax which will set data. The ajax calls that are made bring back data for events, countries and hospitals. Once the data as been returned I am setting the data in localStorage.
If the data already exists in localStorage then I can continue while being offline as I don't have to rely on the ajax calls.
I am having performance issues with this code when running on iPad/mobile devices. Where the hospital data is concerned. There is a large amount of data being returned for hospitals, please see sample of JSON for hospital data below. It can be a little slow on desktop but not nearly as bad.
Can anyone see any improvements to the code I have below to increase the performance.
JSON hospital data example
{ "hospitals" : {
"1" : { "country" : "AD",
"label" : "CAIXA ANDORRANA SEGURETAT SOCIAL"
},
"10" : { "country" : "AE",
"label" : "Advance Intl Pharmaceuticals"
},
"100" : { "country" : "AT",
"label" : "BIO-KLIMA"
},
"1000" : { "country" : "BE",
"label" : "Seulin Nicolas SPRL"
},
"10000" : { "country" : "ES",
"label" : "Dental 3"
},
"10001" : { "country" : "ES",
"label" : "PROTESIS DENTAL MACHUCA PULIDO"
},
"10002" : { "country" : "ES",
"label" : "JUST IMPLANTS, S.L."
},
"10003" : { "country" : "ES",
"label" : "CTRO DENTAL AGRIC ONUBENSE DR.DAMIA"
},
"10004" : { "country" : "ES",
"label" : "HTAL. VIRGEN DE ALTAGRACIA"
},
"10005" : { "country" : "ES",
"label" : "HOSPITAL INFANTA CRISTINA"
}....
/*global document,localStorage,alert,navigator: false, console: false, $: false */
$(document).ready(function () {
//ECMAScript 5 - It catches some common coding bloopers, throwing exceptions. http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
//It prevents, or throws errors, when relatively "unsafe" actions are taken (such as gaining access to the global object).
//It prevents, or throws errors, when relatively "unsafe" actions are taken (such as gaining access to the global object).
"use strict";
//initialise online/offline workflow variables
var continueWorkingOnline, continueWorkingOffline, availableEvents, availableHospitals, availableCountries, availableCities;
continueWorkingOnline = navigator.onLine;
var getLocalItems = function () {
var localItems = [];
availableEvents = localStorage.getItem('eventData');
availableHospitals = localStorage.getItem('hospitalData');
availableCountries = localStorage.getItem('countryData');
if (availableEvents) {
//only create the array if availableEvents exists
localItems[0] = [];
localItems[0].push(availableEvents);
}
if (availableCountries) {
//only create the array if availableCountries exists
localItems[1] = [];
localItems[1].push(availableCountries);
}
if (availableHospitals) {
//only create the array if availableHospitals exists
localItems[2] = [];
localItems[2].push(availableHospitals);
}
if (availableCities) {
//only create the array if availableHospitals exists
localItems[3] = [];
localItems[3].push(availableCities);
}
return localItems;
};
//Check to see if there are 3 local items. Events, Countries, Cities. If true we know we can still run page off line
continueWorkingOffline = getLocalItems().length === 3 ? true: false;
//Does what is says on the tin
var populateEventsDropDown = function (data) {
var eventsDropDown = $('#eventSelection');
var item = data.events;
$.each(item, function (i) {
eventsDropDown.append($('<option></option>').val(item[i].id).html(item[i].name));
});
};
//Called once getData's success call back is fired
var setFormData = function setData(data, storageName) {
//localStorage.setItem(storageName, data);
localStorage.setItem(storageName, data);
};
//This function is only called if continueWorkingOnline === true
var getRemoteFormData = function getData(ajaxURL, storageName) {
$.ajax({
url: ajaxURL,
type: "POST",
data: '',
success: function (data) {
setFormData(data, storageName);
}
});
};
//Function for autoComplete on Hospital data
var autoCompleteDataHospitals = function (sourceData) {
var domID = '#hospitalSearchField';
var country = $('#hiddenCountryID').val();
var items = $.map(sourceData, function (obj) {
if (obj.country === country) {
return obj;
}
});
$(domID).autocomplete({
target: $('#hospitalSuggestions'),
source: items,
callback: function (e) {
var $a = $(e.currentTarget);
$(domID).val($a.data('autocomplete').label);
$(domID).autocomplete('clear');
}
});
};
//Function for autoComplete on Country data
var autoCompleteDataCountries = function (sourceData) {
var domID = '#countrySearchField';
var domHiddenID = '#hiddenCountryID';
var items = $.map(sourceData, function (obj) {
return obj;
});
$(domID).autocomplete({
target: $('#countrySuggestions'),
source: items,
callback: function (e) {
var $a = $(e.currentTarget);
$(domID).val($a.data('autocomplete').label);
$(domID).autocomplete('clear');
$(domHiddenID).val($a.data('autocomplete').value);
//enable field to enter Hospital
$('#hospitalSearchField').textinput('enable');
//Call to autoComplete function for Hospitals
autoCompleteDataHospitals(availableHospitals.hospitals);
}
});
};
if (continueWorkingOnline === false && continueWorkingOffline === false) {
alert("For best results this form should be initiated online. You can still use this but auto complete features will be disabled");
}
if (continueWorkingOnline === true && continueWorkingOffline === false) {
getRemoteFormData('templates/cfc/Events.cfc?method=getEventsArray', 'eventData');
getRemoteFormData('templates/cfc/Countries.cfc?method=getCountriesArray', 'countryData');
getRemoteFormData('templates/cfc/Hospitals.cfc?method=getHospitalsArray', 'hospitalData');
$(document).ajaxStop(function () {
//set the variables once localStorage has been set
availableEvents = JSON.parse(localStorage.getItem("eventData"));
availableHospitals = JSON.parse(localStorage.getItem('hospitalData'));
availableCountries = JSON.parse(localStorage.getItem('countryData'));
//Inserts data into the events drop down
populateEventsDropDown(availableEvents);
autoCompleteDataCountries(availableCountries.countries);
});
}
if (continueWorkingOnline === true && continueWorkingOffline === true) {
//get the localStorage which we know exists because of continueWorkingOffline is true
availableEvents = JSON.parse(localStorage.getItem('eventData'));
availableHospitals = JSON.parse(localStorage.getItem('hospitalData'));
availableCountries = JSON.parse(localStorage.getItem('countryData'));
//Inserts data into the events drop down
populateEventsDropDown(availableEvents);
autoCompleteDataCountries(availableCountries.countries);
}
});
If your bottleneck is downloading that massive json file, the only way to make it less of a bottleneck is to make it smaller or to send less data. For example, you could sort your hospitals by country and store arrays rather than objects with keys to make the json smaller rather than repeating keys over and over.
{
"AD":[
"CAIXA ANDORRANA SEGURETAT SOCIAL"
],
"AE":[
"Advance Intl Pharmaceuticals"
],
...
"ES":[
"Dental 3",
"Dental 4",
"Dental 5"
]
}
if the id field is important, use
...
"ES":[
["10000","Dental 3"],
["10001","Dental 4"],
["10002","Dental 5"]
]
You would of course need to update the rest of your code to use this json format rather than your previous.

Backbone: Set the parent view's model (different from child model)

I have a Parent and Child view. The Child view extends the Parent events with:
initialize : function() {
// don't overwrite parent's events
this.events = _.extend( {}, ExerciseRowView.prototype.events, this.events);
},
However, the Parent expects a ParentModel and the Child expects a ChildModel, so when an event is passed to the Parent, the model is the ChildModel. How can I set the Parent model to be different from the Child model?
Thanks!
Here's the source, as requested.
ParentView aka ExerciseRowView:
var ExerciseRowView = Parse.View.extend( {
tagName : 'div',
className : 'exerciseWrapper',
template : _.template(exerciseElement),
events : {
'click .icon_delete' : 'confirmDelete',
'click .name' : 'showDetailsPopup'
},
confirmDelete : function() {
var that = this;
if(confirm("Are you sure you want to delete this exercise?")) {
this.destroy({
success: function(exercise) {
// log the action
Log.add(Log.ACTION_EXERCISE_DELETED, exercise.get("name"));
that.$el.fadeOut();
}
});
}
},
showDetailsPopup : function() {
(new ExerciseDetailsView({model: (this.model.constructor == Exercise ? this.model : this.model.get("exercise"))})).render();
},
// accept data as a parameter for workoutexercises
render : function(data) {
_.defaults(data, {
exercise: this.model,
Muscle : Muscle,
Equipment : Equipment,
Exercise : Exercise,
Break : Break,
HTMLHelper : HTMLHelper,
User : User
});
$(this.el).html(this.template(data));
return this;
}
});
ChildView aka WorkoutExerciseRowView:
var WorkoutExerciseRowView = ExerciseRowView.extend( {
events : {
"click .icon_randomize" : "changeToRandomExercise"
},
initialize : function() {
// don't overwrite parent's events
this.events = _.extend( {}, ExerciseRowView.prototype.events, this.events);
},
render: function() {
// override the template data with workout exercise template data
return ExerciseRowView.prototype.render.call(this, {
workoutExercise : this.model,
exercise : this.model.get("exercise"),
workoutSection : this.model.get("section"),
isEditable : true,
number : this.options.number,
WorkoutExercise : WorkoutExercise,
WorkoutSection : WorkoutSection
});
},
changeToRandomExercise : function(e) {
// pick a random alternative exercise
var newExerciseId;
do {
newExerciseId = _.keys(this.model.get("alternativeExercises"))[ Math.floor(Math.random() * _.keys(this.model.get("alternativeExercises")).length) ];
} while(newExerciseId == this.model.get("exercise").id);
// grab it
var that = this;
(new Parse.Query(Exercise)).get(newExerciseId, {
success: function(exercise) {
// update the workout exercise
that.model.set("exercise", exercise);
// render it
that.render();
}
});
}
});
Currently (as you can see), I test to see if this.model.constructor == Exercise inside ExerciseRowView. If it is not, I know that I have a WorkoutExercise, inside which is an Exercise, so I use this.model.get("exercise"):
showDetailsPopup : function() {
(new ExerciseDetailsView({model: (this.model.constructor == Exercise ? this.model : this.model.get("exercise"))})).render();
},
This doesn't seem like the cleanest possible solution, though.
what I could think of is that you define function for each view
ParentView
getExercise: function() {
return this.model;
}
ChildView
getExercise: function() {
return this.model.get('exercise');
}
And then change the function
showDetailsPopup: function() {
(new ExerciseDetailsView({model: this.getExercise()})).render();
}
How about that?

Access collection on two views in backbone.js

Hi i have a collection and two views. On my view1 i'm adding data to my collection and view2 will just render and display any changes about the collection. But i can't get it to work. The problem is originally i'm doing this
return new CartCollection();
But they say its a bad practice so i remove changed it. But when i instantiate cart collection on view1 it would add but it seems view2 doesn't sees the changes and renders nothing.
Any ideas?
here is my cart collection.
define([
'underscore',
'backbone',
'model/cart'
], function(_, Backbone, CartModel) {
var CartCollection = Backbone.Collection.extend({
model : CartModel,
initialize: function(){
}
});
return CartCollection;
});
Here is my itemView ( view1 )
AddToCart:function(ev){
ev.preventDefault();
//get data-id of the current clicked item
var id = $(ev.currentTarget).data("id");
var item = this.collection.getByCid(id);
var isDupe = false;
//Check if CartCollection is empty then add
if( CartCollection.length === 0){
CartCollection.add([{ItemCode:item.get("ItemCode"),ItemDescription:item.get("ItemDescription"),SalesPriceRate:item.get("RetailPrice"),ExtPriceRate:item.get("RetailPrice"),WarehouseCode: "Main",ItemType : "Stock",LineNum:1 }]);
}else{
//if check if the item to be added is already added, if yes then update QuantityOrdered and ExtPriceRate
_.each(CartCollection.models,function(cart){
if(item.get("ItemCode") === cart.get("ItemCode")){
isDupe = true;
var updateQty = parseInt(cart.get("QuantityOrdered"))+1;
var extPrice = parseFloat(cart.get("SalesPriceRate") * updateQty).toFixed(2);
cart.set({ QuantityOrdered: updateQty });
cart.set({ ExtPriceRate: extPrice });
}
});
//if item to be added has no duplicates add new item
if( isDupe == false){
var cartCollection = CartCollection.at(CartCollection.length - 1);
var lineNum = parseInt( cartCollection.get("LineNum") ) + 1;
CartCollection.add([{ItemCode:item.get("ItemCode"),ItemDescription:item.get("ItemDescription"),SalesPriceRate:item.get("RetailPrice"),ExtPriceRate:item.get("RetailPrice"),WarehouseCode: "Main",ItemType : "Stock",LineNum:lineNum}]);
}
}
CartListView.render();
}
My cartview (view2)
render: function(){
this.$("#cartContainer").html(CartListTemplate);
var CartWrapper = kendobackboneModel(CartModel, {
ItemCode: { type: "string" },
ItemDescription: { type: "string" },
RetailPrice: { type: "string" },
Qty: { type: "string" },
});
var CartCollectionWrapper = kendobackboneCollection(CartWrapper);
this.$("#grid").kendoGrid({
editable: true,
toolbar: [{ name: "save", text: "Complete" }],
columns: [
{field: "ItemDescription", title: "ItemDescription"},
{field: "QuantityOrdered", title: "Qty",width:80},
{field: "SalesPriceRate", title: "UnitPrice"},
{field: "ExtPriceRate", title: "ExtPrice"}
],
dataSource: {
schema: {model: CartWrapper},
data: new CartCollectionWrapper(CartCollection),
}
});
},
The problem is you've created 2 different instances of CartCollection. So when you update or fetch data into one instance the other does not change but remains the same.
Instead you need to use the same instance of CartCollection across the 2 views (or alternatively keep the 2 insync) .Assuming both views are in the same require.js module you would need to:
1) Instantiate the CartCollection instance and store it somewhere that both views have access to. You could put this in the Router, the parent view, or anywhere else really.
e.g.
var router = Backbone.Router.extend({});
router.carts = new CartCollection();
2) You need need to pass the CartCollection instance to each of your views.
e.g.
var view1 = new ItemView({ collection: router.carts });
var view2 = new CartView({ collection: router.carts });
You may also want to just pass the Cart model to the CartView instead of the entire collection.
e.g.
var cartModel = router.carts.get(1);
var view2 = new CartView({ model: cartModel });

Categories

Resources