What I am trying to accomplish is getting ccoenraets backbone-cellar example to work with multiple tables in the cellar database.
What I have tried so far by altering 'part-1' of the example:
I altered the database: I duplicated the existing 'wine' table to
'winedetail'. Then I deleted all the columns in 'wine' exept for id
and name.
I've changed the functions in index.php to:
function getWines() {
$sql = "select name,id FROM wine ORDER BY name";
try {
$db = getConnection();
$stmt = $db->query($sql);
$wines = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
// echo '{"wine": ' . json_encode($wines) . '}';
echo json_encode($wines);
} catch(PDOException $e) {
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
}
function getWine($id) {
$sql = "SELECT * FROM winedetail WHERE id=:id";
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$stmt->bindParam("id", $id);
$stmt->execute();
$wine = $stmt->fetchObject();
$db = null;
echo json_encode($wine);
} catch(PDOException $e) {
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
}
The error I'm getting in chome console :
Uncaught ReferenceError: grapes is not defined
I have not altered the index.html and main.js. For reference I will post them here as well:
main.js
// Models
window.Wine = Backbone.Model.extend();
window.WineCollection = Backbone.Collection.extend({
model:Wine,
url:"../api/wines"
});
window.WineListView = Backbone.View.extend({
tagName:'ul',
initialize:function () {
this.model.bind("reset", this.render, this);
},
render:function (eventName) {
_.each(this.model.models, function (wine) {
$(this.el).append(new WineListItemView({model:wine}).render().el);
}, this);
return this;
}
});
// Views
window.WineListItemView = Backbone.View.extend({
tagName:"li",
template:_.template($('#tpl-wine-list-item').html()),
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
window.WineView = Backbone.View.extend({
template:_.template($('#tpl-wine-details').html()),
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"list",
"wines/:id":"wineDetails"
},
list:function () {
this.wineList = new WineCollection();
this.wineListView = new WineListView({model:this.wineList});
this.wineList.fetch();
$('#sidebar').html(this.wineListView.render().el);
},
wineDetails:function (id) {
this.wine = this.wineList.get(id);
this.wineView = new WineView({model:this.wine});
$('#content').html(this.wineView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
index.html
<!DOCTYPE HTML>
<html>
<head>
<title>Backbone Cellar</title>
</head>
<body>
<div id="header"><span class="title">Backbone Cellar</span></div>
<div id="sidebar"></div>
<div id="content">
<h2>Welcome to Backbone Cellar</h2>
<p>
This is a sample application part of of three-part tutorial showing how to build a CRUD application with Backbone.js.
</p>
</div>
<!-- Templates -->
<script type="text/template" id="tpl-wine-list-item">
<a href='#wines/<%= id %>'><%= name %></a>
</script>
<script type="text/template" id="tpl-wine-details">
<div class="form-left-col">
<label>Id:</label>
<input type="text" id="wineId" name="id" value="<%= id %>" disabled />
<label>Name:</label>
<input type="text" id="name" name="name" value="<%= name %>" required/>
<label>Grapes:</label>
<input type="text" id="grapes" name="grapes" value="<%= grapes %>"/>
<label>Country:</label>
<input type="text" id="country" name="country" value="<%= country %>"/>
<label>Region:</label>
<input type="text" id="region" name="region" value="<%= region %>"/>
<label>Year:</label>
<input type="text" id="year" name="year" value="<%= year %>"/>
</div>
<div class="form-right-col">
<img height="300" src="../pics/<%= picture %>"/>
<label>Notes:</label>
<textarea id="description" name="description"><%= description %></textarea>
</div>
</script>
<!-- JavaScript -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
<script src="http://documentcloud.github.com/backbone/backbone-min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Try with
wineDetails:function (id) {
var wine = this.wineList.get(id);
wine.fetch({
success: function(){
console.log(wine);
var wineView = new WineView({model: wine});
$('#content').html(wineView.render().el);
}
});
}
Make sure that the model printed int he console has a grapes value. Doing the above makes the view wait until the model has been fetched before trying to display it. If the code above works, it means that either:
when you're fetching the collection, you're missing the grapes value on your models
you're not waiting for the collection to be fetched before displaying the wine details
Note also that it looks like if a user goes directly to url "wines/1" your app will crash, becasue there's no collection instance.
Related
I've been all the day searching for a way to accomplish this, any help would be really appreciated.
We want to create a date selector, but not using the javascript Date format but a string 'YYYY-MM-DD'. So we tried to create a custom type which has two inputs, a type="date" so that the user can introduce the date he wants to and a hidden type="text" which stores the actual model.
It looked well at the beginning:
formlyConfig.setType({
'name': 'nativeDateSelect',
template: `
<input type="text" class="form-control" style="display: none;"
ng-model="model[options.key]" />
<input type="date" class="form-control"
ng-model="dateValue"
formly-skip-ng-model-attrs-manipulator />
`,
wrapper: ['bootstrapLabel', 'bootstrapHasError'],
controller: function ($scope, moment) {
$scope.dateValue = null;
$scope.$watch('model[options.key]', function (newValue) {
if (angular.equals($scope.dateValue, moment(newValue).toDate())) return;
$scope.dateValue = moment(newValue).toDate();
});
$scope.$watch('dateValue', function (newValue) {
if (angular.equals($scope.model[$scope.options.key], moment(newValue).format('YYYY-MM-DD'))) return;
$scope.model[$scope.options.key] = moment(newValue).format('YYYY-MM-DD');
});
},
'defaultOptions': {
'extras': {
'validateOnModelChange': true
}
}
});
This is it, it actually works, changing any of the input will modify the other one.
But here's the problem, once I introduce this type in an actual form and try to work with it, lets say for example adding an onChange function, it won't trigger as I'm not doing a change on the text input.
/* global angular */
(function() {
'use strict';
var app = angular.module('formlyExample', ['formly', 'formlyBootstrap', 'angularMoment'], function config(formlyConfigProvider) {
// set templates here
formlyConfigProvider.setType([
{
'name': 'nativeDateSelect',
template:
'<input type="text" class="form-control" style="display: block;" ' +
'ng-model="model[options.key]" /> ' +
'<input type="date" class="form-control" ' +
'ng-model="dateValue" ' +
'formly-skip-ng-model-attrs-manipulator />',
wrapper: ['bootstrapLabel', 'bootstrapHasError'],
controller: function ($scope, moment) {
$scope.dateValue = null;
$scope.$watch('model[options.key]', function (newValue) {
if (angular.equals($scope.dateValue, moment(newValue).toDate())) return;
$scope.dateValue = moment(newValue).toDate();
});
$scope.$watch('dateValue', function (newValue) {
if (angular.equals($scope.model[$scope.options.key], moment(newValue).format('YYYY-MM-DD'))) return;
$scope.model[$scope.options.key] = moment(newValue).format('YYYY-MM-DD');
});
},
'defaultOptions': {
'extras': {
'validateOnModelChange': true
}
}
}
]);
});
app.controller('MainCtrl', function MainCtrl(formlyVersion) {
var vm = this;
// funcation assignment
vm.onSubmit = onSubmit;
vm.exampleTitle = 'Default Options'; // add this
vm.model = {};
vm.fields = [
{
'type': 'nativeDateSelect',
'key': 'startDate',
'expressionProperties': {
'templateOptions.label': '\'startDate\''
},
'templateOptions': {
'required': true,
'onChange': function (modelValue, field, scope) {
alert('startDate: ' + modelValue);
}
}
}
];
vm.originalFields = angular.copy(vm.fields);
// function definition
function onSubmit() {
alert(JSON.stringify(vm.model), null, 2);
}
});
})();
<!DOCTYPE html>
<html>
<head>
<!-- Twitter bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
<!-- apiCheck is used by formly to validate its api -->
<script src="//npmcdn.com/api-check#latest/dist/api-check.js"></script>
<!-- This is the latest version of angular (at the time this template was created) -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<!-- This is the current state of master for formly core. -->
<script src="//npmcdn.com/angular-formly#latest/dist/formly.js"></script>
<!-- This is the current state of master for the bootstrap templates -->
<script src="//npmcdn.com/angular-formly-templates-bootstrap#latest/dist/angular-formly-templates-bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-moment/0.10.3/angular-moment.min.js"></script>
<title>Angular Formly Example</title>
</head>
<body ng-app="formlyExample" ng-controller="MainCtrl as vm">
<div>
<h1>Changing our model from the controller</h1>
<hr />
<form ng-submit="vm.onSubmit()" novalidate>
<formly-form model="vm.model" fields="vm.fields" form="vm.form">
<button type="submit" class="btn btn-primary submit-button">Submit</button>
</formly-form>
</form>
<h2>Model</h2>
<pre>{{vm.model | json}}</pre>
<h2>Fields <small>(note, functions are not shown)</small></h2>
<pre>{{vm.originalFields | json}}</pre>
<h2>Form</h2>
<pre>{{vm.form | json}}</pre>
</div>
</body>
</html>
Do anyone know how to manage a scenario like this? I need one input to actually input and the other to be the model, so this one should track changes/validation/etc. whenever it is modified.
Thanks again!
I'm trying to use backbone to show on a page the result from an API call, I would like to iterate over the collection and create one entry for every element of the collection within my html. It seems I'm missing something cause I see the template tag rendered but none of my items are there. What's the problem with my code?
here the html
<div class="form-group" id="main">
<% _.each(collection, function(car) { %>
<div class="form-group">
<input class="form-control" /><%= car.get("model") %>
</div>
<% }); %>
</div>
and here js
var CarView = Backbone.View.extend({
el: "#main",
template: _.template($("#main").html()),
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template({collection: [{id:1, model:"ford"}, {id:2,model:"kia"}]}));
return this;
}
});
var carView = new CarView();
here the fiddle: https://jsfiddle.net/e5hg6rzp/3/
First of all I suggest you to keep your template in <script type='text'/template> ... </script> tag. Secondly you are using .get() method inside your template on plain objects which are do not have this method. In your example you can access property through a . -
<div class="form-group">
<input class="form-control" /><%= car.model %>
</div>
Check this fiddle
If you want to use Backbone.Collection when you should create Car Collection and Car Model:
var data = [{
id: 1,
model: "ford"
}, {
id: 2,
model: "kia"
}];
var CarView = Backbone.View.extend({
el: "#main",
template: _.template($("#templ").html()),
initialize: function() {
this.render();
},
render: function() {
return this.$el.html(this.template(new CarCollection(data)))
}
});
var CarModel = Backbone.Model.extend({
defaults: {
id: '',
model: ''
}
})
var CarCollection = Backbone.Collection.extend({
model: CarModel
})
var carView = new CarView();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<div class="container">
<div class="form-inline panel panel-default">
<div class="form-group" id="main">
</div>
</div>
</div>
<script type='text/template' id='templ'>
<% _.each(models, function(car) { %>
<div class="form-group">
<input class="form-control" />
<%= car.get('model') %>
</div>
<% }); %>
</script>
I'm following this plunker to create a typeahead in my project.
http://plnkr.co/edit/ZjpJxXkl0v5LhQdxcqWn?p=preview
app.js (not working with my API)
$scope.getAddress = function(viewValue) {
var params = {address: viewValue, sensor: false};
return $http.get('http://maps.googleapis.com/maps/api/geocode/json', {params: params})
.then(function(res) {
console.log(res);
return res.data.results;
});
};
Index.html
<!-- Using an async data provider -->
<div class="form-group">
<label><i class="fa fa-home"></i> Address <small>(async via maps.googleapis.com)</small></label>
<input
type="text"
class="form-control"
ng-model="selectedAddress"
data-animation="am-flip-x"
ng-options="address.formatted_address as address.formatted_address for address in getAddress($viewValue)"
placeholder="Enter address"
bs-typeahead>
</div>
Inn my case i'm fetching data from a .net API. When i console log the results from the API I can see the array is returning from the API. but when i try to return it to the typeahead the data isn't displayed. however if i try to create an array of mock objects and then manually insert the data into the array aswell the results appear in the typeahead.
app.js (data is displayed)
$scope.getAddress = function(viewValue) {
var params = {address: viewValue, sensor: false};
return $http.get('http://maps.googleapis.com/maps/api/geocode/json', {params: params})
.then(function(res) {
console.log(res);
[{formatted_address: "Alabama"},{formatted_address: "Arkansas"},{formatted_address: res.data.results[0]}]
return res.data.results;
});
};
why could this be happening and how would i fix it?
I can't see your full solution so it's difficult to say what did you missed but please see below for working solution.
var app = angular.module('app', ['mgcrea.ngStrap']);
app.controller('firstCtrl', function($scope, $http) {
$scope.getAddress = function(viewValue) {
var params = {
address: viewValue,
sensor: false
};
return $http.get('http://maps.googleapis.com/maps/api/geocode/json', {
params: params
})
.then(function(res) {
return res.data.results;
});
};
});
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<script src="//mgcrea.github.io/angular-strap/dist/angular-strap.js" data-semver="v2.1.3"></script>
<script src="//mgcrea.github.io/angular-strap/dist/angular-strap.tpl.js" data-semver="v2.1.3"></script>
<body ng-app="app">
<div ng-controller="firstCtrl">
<div class="form-group">
<label><i class="fa fa-home"></i> Address <small>(async via maps.googleapis.com)</small>
</label>
<input type="text" class="form-control" ng-model="selectedAddress" data-animation="am-flip-x" ng-options="address.formatted_address as address.formatted_address for address in getAddress($viewValue)" placeholder="Enter address" bs-typeahead>
</div>
</div>
</body>
im trying to do a little example to learn on using cachefactory but i get the error that
"Argument 'CacheSampleController' is not a function"
this is my app
var eventsApp = angular.module('eventsApp', ['ngResource'])
.factory('myCache', function($cacheFactory) {
return $cacheFactory('myCache', {capacity:3});
});
this is the hmtl file:
<div ng-controller="CacheSampleController" style="padding-left:20px; padding-right:20px">
key: <input type="text" ng-model="key"/><br/>
value: <input type="text" ng-model="value"/><br/>
<button type="button" class="btn" ng-click="addToCache(key, value)">Add To Cache</button><br/>
<br/>
<br/>
<input type="text" ng-model="keyToRead"/><br/>
<h3>Value from cache: {{readFromCache(keyToRead)}}</h3>
<h3>Cache Stats: </h3>{{getCacheStats()}}
</div>
and this is the controller
eventsApp.controller('CacheSampleController',
function CacheSampleController($scope, myCache) {
$scope.addToCache = function(key, value) {
myCache.put(key, value);
};
$scope.readFromCache = function(key) {
return myCache.get(key);
};
$scope.getCacheStats = function() {
return myCache.info();
};
}
);
im not sure if it could be a syntax error or something else that im just not seeing?
thanks
Add the ng-app to the htm elemvent
<html ng-app="eventsApp">
Load your js files in this order
<script src="app.js"></script>
<script src="script.js"></script>
And your service should look like this
var eventsApp = angular.module('eventsApp', [])
.factory('myCache', function($cacheFactory) {
return $cacheFactory('myCache', {capacity:3});
});
Check this: plnkr
You must include (CacheSampleController.js) to your page like this:
<script src="/js/controllers/CacheSampleController.js"></script>
Add it after (app.js) line and I am sure it will work.
I can't get this thing to add when i click the add button. I'm completely new to this.
JS code
function RecipeApp() {
var _Ingredient=Backbone.Model.extend(COOKBOOK.Domain.Ingredient),
_Ingredients = Backbone.Collection.extend({
model: _Ingredient
}),
_IngredientView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
},
render: function () {
var templateid = this.model.get('ViewID');
$(this.el).html(Mustache.to_html($("#"+templateid).html(),this));
}
}),
_AddView = Backbone.View.extend({
id:"divAddIngredient",
events: {
"click .btn": "create"
},
render: function () {
var tmpAddAnIngredient = $("#tmpMasterView").html(),
$submain = $("#submain");
$submain.html(Mustache.to_html(tmpAddAnIngredient, COOKBOOK.Domain));
},
initialize: function (ingredients) {
console.log("init enter");
this.render();
this._Ingredients = ingredients;
this._Ingredients.bind('add', this.add, this);
console.log("init leave");
},
//added functions
create: function () {
console.log("create");
var typename = this.$(".typeName").val(),
ingredient = _.detect(COOKBOOK.Domain.Ingredients, function (i) { i.TypeName === typename });
if (!!ingredient) {
this._Ingredients.create(ingredient);
}
},
add: function (ingredient) {
console.log('add');
var view = new _IngredientView(ingredient);
this.$("#divIngredients").append(view.render().el);
}
});
this.Ingredients = new _Ingredients();
this.AddView = new _AddView(this.Ingredients);
}
$(function () {
window.app = new RecipeApp();
//
});
And here is the mustache template
<script id="tmpTempDirectoryIngredient" type="text/html">
<div class="block-message">
<form>
<fieldset>
<legend>Create a Temporary Directory</legend>
<div class="clearfix">
<input type="text" name="DirectoryName" class="DirectoryName" />
</div>
</fieldset>
</form>
</div>
</script>
<script id="tmpMasterView" type="text/html">
<div class="block-message info" id="divAddIngredient">
<form>
<fieldset>
<legend>Add an Ingredient</legend>
<div class="clearfix">
<select class="typeName">
{{#Ingredients}}
<option value="{{TypeName}}">{{Name}}</option>
{{/Ingredients}}
</select>
</div>
<div class="clearfix">
<input type="button" class="btn primary" value="Add Ingredient" />
</div>
</fieldset>
</form>
</div>
<hr />
<div id="divIngredients">
</div>
</script>
it started working as soon as i explicitly set the el property of the _AddView to a tag that existed when the _AddView was created.
$(function(){
new Apps({el:$("body"),'records':[1,2,3,4,5]});
});
Here need to give el.
because of only after DOM is generating.....
The way you are passing Ingredients to the _AddView initialize, they will be accessible by this.options (see http://documentcloud.github.com/backbone/#View-constructor).
I think a better way is pass your ingredients collection into you _AddView like this:
this.AddView = new _AddView({collection: this.Ingredients});
Then within your definition of your view, always refer to this.collection instead of this._Ingredients. That is I think a more standard way to do it.