I'm new to Backbone. here is my model:
window.Image = Backbone.Model.extend({
defaults: {
id: "",
url: ""
}
});
window.ImageCollection = Backbone.Model.extend({
model: Image
});
window.TargetDemoGraphic = Backbone.Model.extend({
defaults: {
targetDemographicId: "",
targetDemographicDescription: "",
checked: false
}
});
window.TargetDemoGraphicCollection = Backbone.Collection.extend({
model: TargetDemoGraphic,
url: ServiceUrl + "/targetDemoGraphic/getAll"
});
window.Promotion = Backbone.Model.extend({
url: function() {
return ServiceUrl + "/promotion/get/" + encodeURIComponent(this.id);
},
defaults: {
id: "",
item: Item,
start: "",
end: "",
title: "",
newPrice: "",
shortContent: "",
detailContent: "",
minLoyaltyPoint: "",
membershipRequired: "",
loyaltyPointRequired: "",
loyaltyPointExchangeRequired: "",
displayOnBanner: "",
images: ImageCollection,
bannerImage: Image,
targetDemoGraphics: TargetDemoGraphicCollection
}
}
window.PromotionCollection = Backbone.Collection.extend({
model: Promotion,
url: ServiceUrl + "/promotion/getAll"
});
My problem is when printing out the nested collection targetDemoGraphics in the template, the length of the collection is always 0.
I'm so confused because the nested collection images work well, it returns correct value.
My backbone view:
window.PromotionReview = Backbone.View.extend({
initialize : function(options) {
_.bindAll(this, 'beforeRender', 'render', 'afterRender');
var _this = this;
this.render = _.wrap(this.render, function(render) {
_this.beforeRender();
render();
_this.afterRender();
return _this;
});
this.render();
},
beforeRender : function() {
},
render : function() {
var promotion = this.model.toJSON();
//promotion.targetDemoGraphics.length returns = 4
//promotion.images.length = 4
$(this.el).html(this.template(promotion));
return this;
}
});
And the template:
<% if (targetDemoGraphics != null && targetDemoGraphics.length > 0) { %>
<% for (var idx in targetDemoGraphics) { %>
<p><%= targetDemoGraphics[i].label %></p>
<% } %>
<% } %>
//targetDemoGraphics.length = 0???
...
<% if (images != null && images.length > 0) { %>
<% for (var idx in images) { %>
<a href="<%=ServiceUrl + '/images/' + images[idx].url %>" data-gallery>
<img style="min-height:100px;height:100px;min-width:100px;width:100px;" src="<%=ServiceUrl + '/images/' + images[idx].url %>">
</a>
<% } %>
<% } %>
//images.length = 4, correct value ???
When printing out the promotion, I got 2 targetDemoGraphics, one contains 4 objects, other one is a function???
If you are nesting collections you better try and use some Backbone extension. Check Backbone Associations. Using some extension would help you in binding events on the nested Models and Collections as well and definitely other advantages.
Related
I'm using EasyAutocomplete to select some data.
The problem I'm having is that I only can use one autocomplete input. I tried to find a solution but I didn't find anything in the documentation.
My code:
<%= form_tag('/search', local: true, method: :get, class: 'form-group row' ) do %>
<div class="col-md-5">
<%= text_field_tag(:q, nil, data: { behavior: 'autocomplete-lang' }, placeholder: 'Lenguaje', class: 'form-control') %>
</div>
<% end %>
<%= form_tag('/search', local: true, method: :get, class: 'form-group row' ) do %>
<div class="col-md-5">
<%= text_field_tag(:q, nil, data: { behavior: 'autocomplete-ide' }, placeholder: 'Lenguaje', class: 'form-control') %>
</div>
<% end %>
Controller:
def search
#lang = Content.ransack(name_cont: params[:q],types_id_eq: 1).result(distinct: true)
#ide = Content.ransack(name_cont: params[:q],types_id_eq: 4).result(distinct: true)
respond_to do |format|
format.html {}
format.json {
#lang = #lang.limit(5)
#ide = #ide.limit(5)
}
end
end
Js:
document.addEventListener("turbolinks:load", function() {
var $input = $("[data-behavior='autocomplete-lang']");
var options = {
getValue: "name",
url : function(phrase) {
return "/search.json?q=" + phrase;
},
categories: [
{
listLocation: "lang"
}
],
list: {
onChooseEvent: function () {
var id = $input.getSelectedItemData().id;
console.log(id);
}
}
};
$input.easyAutocomplete(options);
});
document.addEventListener("turbolinks:load", function() {
var $input_ide = $("[data-behavior='autocomplete-ide']");
//IDE
var options_ide = {
getValue: "name",
url : function(phrase) {
return "/search.json?q=" + phrase;
},
categories: [
{
listLocation: "ide"
}
],
list: {
onChooseEvent: function () {
var id = $input_ide.getSelectedItemData().id;
console.log(id);
}
}
};
$input_ide.easyAutocomplete(options_ide);
});
So, I only can use once the easyAutocomplete, if I use it twice the behavior is not correct and only one input work.
I really need a hand to solve this, is must be pretty simple, but I can't get it.
I faced the same problem and solved it by adding unique "id"s to each text field.
In JavaScript, instead of writing:
getValue: "name"
You can write something similar to:
getValue: function (element) {
return element.lang+ element.ide
}
You can follow the discussion on this github link.
I've got a collection of models with different properties and I need to render some of them inside the <select> tag, each model as an <option>. The view that renders this collection is nested in another view. Here is my collection:
var UserCollection = Backbone.Collection.extend({
url: 'http://localhost:3000',
developers: function () {
var developers = this.where({role: "developer"});
return new UserCollection(developers);
}
});
And this is my view for the select tag:
var InterviewersView = Backbone.View.extend({
initialize: function () {
this.template = _.template(interviewersTemplate);
this.collection = new UserCollection();
this.collection.fetch();
this.interviewers = this.collection.developers();
},
render: function () {
this.$el.html(this.template());
return this;
}
});
and this is my template:
<label>Interviewer</label>
<select class="form-control" id="js-interviewer-selector">
<% _.each(this.interviewers.toJSON(), function(interviewer) { %>
<option value="<%= interviewer.login %>">
<%= interviewer.firstName %> <%= interviewer.lastName %>
</option>
<% }) %>
</select>
The template renders inside another view properly and exactly as I need, but there's no options inside the select tag and it is empty. What am I doing wrong?
Repo with my project
Try to pass your collection to your view like this
render: function () {
var that = this;
that.$el.html(that.template({interviewers: that.interviewers}));
return this;
}
and in your template use underscore _.each function to dive collection to individual interviwer like this
<select class="form-control" id="js-interviewer-selector">
<% _.each(interviewers, function(interviewer) { %>
<option value="<%= interviewer.login %>">
<%= interviewer.firstName %> <%= interviewer.lastName %>
</option>
<% }) %>
</select>
It must work now:)
So, the problem was the same as in this question -- because of asynchronous nature of .fetch() method the collection was loaded after the view was rendered, so it received nothing. So, removing .fetch() method from initialize and adding it to render worked. Here's the complete code:
var InterviewersSelect = Backbone.View.extend({
initialize: function () {
this.template = _.template(interviewersTemplate);
this.collection = new UserCollection();
},
render: function () {
var self = this;
this.collection.fetch({
data: {
role: "developer"
},
success: function(collection) {
var interviewers = collection.map(function(item) {
return item.toJSON();
});
self.$el.html(self.template({interviewers: interviewers}));
}
});
return this;
}
});
I'm creating a page using Angular which has three controllers. They are loaded using ng-show directive. Each controller has a button with ng-click which calls a function. The function is simple it hides one controller and makes the next one visible.
The problem: this function works for one controller, but for some reason it doesn't work for the other one. Precisely, levelsView controller is loaded, but cardsView is not. CardsView becomes visible if I set ng-show to true in HTML page, but not through the function. I've spent a few days trying to find the problem and I can't, please help me to solve this "mystery".
HTML page:
<main>
<div class="bottom-container">
<div ng-controller="rulesCtrl as rulesView" ng-hide="rulesView.gameMetrics.levelsActive || rulesView.gameMetrics.cardsActive"
class="rules">
<p><span class="bold large-text">Rules: </span>Find a pair for the HTML opening and closing tag or match CSS value with a property within given time. <button class="link-button" data="#rulesHide">Read more...</button></p>
<div class="rules-details" id="rulesHide">
<p><span class="bold large-text">HTML: </span>For HTML a pair would be <span class="bold"><div> + </div></span>.</p>
<p><span class="bold large-text">CSS: </span>For CSS an example could be the property <span class="bold">display</span>. It has values: <span class="bold">block, inline, inline-block </span>etc. A pair would be <span class="bold">display + block.</span></p>
<p>The main challange is to find the pairs fast. The harder the level the shorter will be the time given.</p>
</div>
<button class="button btn-start" ng-click="rulesView.showLevels()">Start</button>
</div>
<div ng-controller="levelsCtrl as levelsView" ng-show="levelsView.gameMetrics.levelsActive" class="levels">
<h2>Select Level:</h2>
<button class="button btn-easy" ng-click="levelsView.showCards()">Easy</button>
<button class="button btn-medium">Medium</button>
<button class="button btn-hard">Hard</button>
</div>
<div ng-controller="cardsCtrl as cardsView" ng-show="cardsView.gameMetrics.cardsActive" class="game-field">
<h2>Find the tag pairs on time.</h2>
<span class="fa fa-clock-o fa-lg"></span>
<span class="game-timer">{{ cardsView.timeLeft }}</span>
<span class="game-timer-msg">{{ cardsView.timesUp }}</span>
<div class="flex-container">
<div class="flex-item"
ng-repeat="cardsData in cardsView.data">
{{ cardsData.text }}
</div>
</div>
</div>
</div>
</main>
Factory with data:
(function(){
angular
.module("syntaxPairing")
.factory("gameMetrics", GameMetrics);
GameMetrics.$inject = ["DataService"];
function GameMetrics(DataService){
var gameObject = {
rulesActive: true,
levelsActive: false,
cardsActive: false,
changeVisibility: changeVisibility
};
return gameObject;
function changeVisibility(metric, state){
if(metric === "rulesView"){
gameObject.rulesActive = state;
}else if(metric === "levelsView"){
gameObject.levelsActive = state;
}else if(metric === "cardsView"){
gameObject.cardsActive = state;
}
return false;
}
}
})();
First controller (rulesView.js):
(function(){
angular
.module("syntaxPairing")
.controller("rulesCtrl", RulesController);
RulesController.$inject = ["gameMetrics", "DataService"];
function RulesController(gameMetrics){
var vm = this;
vm.gameMetrics = gameMetrics;
vm.showLevels = showLevels;
function showLevels(){
gameMetrics.changeVisibility("rulesView", false);
gameMetrics.changeVisibility("levelsView", true);
}
}
})();
Second controller (levelsView.js):
(function(){
angular
.module("syntaxPairing")
.controller("levelsCtrl", LevelsController);
LevelsController.$inject = ["gameMetrics", "DataService"];
function LevelsController(gameMetrics){
var vm = this;
vm.gameMetrics = gameMetrics;
vm.showCards = showCards;
function showCards(){
gameMetrics.changeVisibility("levelsView", false);
gameMetrics.changeVisibility("rulesView", false);
gameMetrics.changeVisibility("cardsView", true);
}
}
})();
Third controller (cardsView.js):
(function(){
angular
.module("syntaxPairing")
.controller("cardsCtrl", CardsController);
CardsController.$inject = ["gameMetrics", "DataService", "$timeout"];
function CardsController(gameMetrics, DataService, $timeout){
var vm = this;
vm.data = DataService.cardsData;
vm.timeLeft = 5;
vm.onTimeout = onTimeout;
function onTimeout(){
vm.timeLeft--;
if(vm.timeLeft > 0){
mytimeout = $timeout(onTimeout, 1000);
}else{
vm.timesUp = "The time is up!";
}
}
var mytimeout = $timeout(onTimeout, 1000);
}
})();
DataService:
(function(){
angular
.module("syntaxPairing")
.factory("DataService", DataFactory);
function DataFactory(){
var dataObject = {
cardsData: cardsData
};
return dataObject;
}
var cardsData = [
{
type: "text",
text: "<html>",
selected: null,
correct: null
},
{
type: "text",
text: "</html>",
selected: null,
correct: null
},
{
type: "text",
text: "<header>",
selected: null,
correct: null
},
{
type: "text",
text: "</header>",
selected: null,
correct: null
},
{
type: "text",
text: "<body>",
selected: null,
correct: null
},
{
type: "text",
text: "</body>",
selected: null,
correct: null
},
{
type: "text",
text: "<p>",
selected: null,
correct: null
},
{
type: "text",
text: "</p>",
selected: null,
correct: null
},
{
type: "text",
text: "<script>",
selected: null,
correct: null
},
{
type: "text",
text: "</script>",
selected: null,
correct: null
},
{
type: "text",
text: "<span>",
selected: null,
correct: null
},
{
type: "text",
text: "</span>",
selected: null,
correct: null
}
]
})();
In third controller i.e. cardsCtrl, you are missing gameMetrics. So cardsView.gameMetrics.cardsActive is not changed.
Just add the below line, it works perfectly.
vm.gameMetrics = gameMetrics;
http://jsbin.com/zeredah/edit?html,js,output
I would create a master template for generating other templates. The only method I found is this one:
var test_tpl_master = _.template(
"StackOverflow <%= type %> question number <%= num %>"
);
var test_tpl_1 = _.template(test_tpl_master({
"type": "good",
"num": "<%= num %>"
}));
var test_tpl_2 = _.template(test_tpl_master({
"type": "stupid",
"num": "<%= num %>"
}));
Is there not a more simple and elegant way?
You can create a function that will act as a proxy to your master and will fill the the variables you want.
For example, let's say you have
var prefill_template = function(tpl, defs) {
return function(data) {
return tpl(_.extend({}, data, defs));
}
}
You can then create your subtemplates functions by
var test_tpl_1 = prefill_template(test_tpl_master, {
"type": "good"
});
var test_tpl_2 = prefill_template(test_tpl_master, {
"type": "stupid"
});
and use them as any other template :
console.log(test_tpl_1({
num: 1
}));
console.log(test_tpl_2({
num: 1
}));
And a demo http://jsfiddle.net/nikoshr/qshb1zrx/
I currently have my code like this but the first name appears the way it is being shown. I am trying to figure out what this is in jade, because this is not right.
Jade File
div.centerContent
script(type="text/javascript", src="/js/main.js")
h4 User goes here with equal before it no space
div#user
p!= "<%=firstName%>"
| <%=lastName%>
p!="<%= email %>"
p <%=phone%>
p <%=birthday%>
button.edit Edit
script(id="userTemplate", type ="text/template")
p <%=firstName%> <%=lastName%>
p <%=email%>
p <%=phone%>
p <%=birthday1%>
button.edit Edit
script(id="userEditTemplate", type ="text/template")
div
form(action="#")
input(type="text", class="firstName", value="<%= firstName %>") input(type="text", class="lastName", value="<%= lastName %>")
input(type="email", class="email", value="<%= email %>")
input(type="date", class="birthday", value="<%= birthday %>")
button.save Save
button.cancel Cancel
hr
Here I will include my main.js file maybe I am doing something wrong there.
main.js
(function () {
window.App = {
Models: {},
Collections: {},
Views: {},
Templates: {},
Router: {}
};
// MODEL
App.Models.User = Backbone.Model.extend({
defaults: {
firstName: 'first',
lastName: 'last',
email: 'Email',
phone: '222',
birthday: 'date'
},
validate: function (attrs) {
if (!attrs.firstName) {
return 'You must enter a real first name.';
}
if (!attrs.lastName) {
return 'You must enter a real last name.';
}
if (attrs.email.length < 5) {
return 'You must enter a real email.';
}
if (attrs.phone.length < 10 && attrs.phone === int) {
return 'You must enter a real phone number, if you did please remove the dash and spaces.';
}
if (attrs.city.length < 2) {
return 'You must enter a real city.';
}
},
initialize: function() {
user.on('invalid', function (model, invalid) {
console.log(invalid);
});
}
});
//VIEW
App.Views.User = Backbone.View.extend({
model: App.Models.User,
//tagName: 'div',
//id: 'user',
//className: 'userProfile',
template: _.template($("#userTemplate").html()),
editTemplate: _.template($("#userEditTemplate").html()),
initialize: function (){
}
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events: {
'click button.edit': 'editProfile',
// 'click button.save': 'saveEdits',
'click button.cancel': 'cancelEdits'
},
editProfile: function () {
this.$el.html(this.editTemplate(this.model.toJSON()));
},
cancelEdits: function() {
this.render();
}
});
//start history service
Backbone.history.start();
var user = new App.Views.User({el: 'div #user'});
user.render();
})();
As far as Jade sees, "<%= firstName %>" is just a String literal. But, it will HTML encode it:
<input type="text" class="firstName" value="<%= firstName %>">
To keep the value as-is, add a ! before the = to skip encoding.
input(type="text", class="firstname", value!="<%= firstName %>")
<input type="text" class="firstName" value="<%= firstName %>">
From the documenation:
Code buffered by = is escaped by default for security, however to output unescaped return values you may use !=:
p!= aVarContainingMoreHTML
Also note that, if you're using an older version of Jade, the contents of script elements may be treated in whole as text literals.
Jade version 0.31.0 deprecated implicit text only support for scripts and styles. To fix this all you need to do is add a . character after the script or style tag.
Before 0.31.0, your view would render as (abridged):
<script id="userEditTemplate" type="text/template">
div.userProfile
form(action="#")
# ...
</script>