ng-model failing to bind to scope in wizard application - javascript

I am having an issue where ng-model is failing to bind to scope.
This section of my web app is set up like a wizard with three steps.
I want to include validation on the first step to prevent the user from reaching the second step if certain requirements are not met. However, in order to this, I need to use ng-model to bind firstName and secondName to the scope.
Here is my code so far, I've also included a plunker here:
wizard.html
<div id="wizard-container" ng-controller="WizardCtrl as vm">
<div id="wizard-step-container">
<ul class="nav nav-pills nav-justified">
<li ng-repeat="step in vm.steps" ng-class="{'active':step.step == vm.currentStep}"><a ng-click="vm.gotoStep(step.step)" href="">{{step.step}}. {{step.name}}</a></li>
</ul>
</div>
<div id="wizard-content-container">
<div ng-include src="vm.getStepTemplate()"></div>
</div>
<div id="wizard-navigation-container">
<div class="pull-right pull-right-padding">
<span class="btn-group">
<button ng-disabled="vm.currentStep <= 1" class="btn btn-default" name="previous" type="button" ng-click="vm.gotoStep(vm.currentStep - 1)"></i>Previous</button>
<button ng-disabled="vm.currentStep >= vm.steps.length" class="btn btn-primary" name="next" type="button" ng-click="vm.gotoStep(vm.currentStep + 1)">Next</button>
</span>
<button ng-disabled="vm.currentStep != vm.steps.length" class="btn btn-success" name="next" type="button" ng-click="vm.save()">Save</button>
</div>
</div>
</div>
step1.html
<div class="row">
<h3 class="text-center">Step 1: Please enter your full name</h3>
<br/>
<div class="col-md-6">
<input type="email" class="form-control" placeholder="First Name" ng-model="formData.firstName">
</div>
<div class="col-md-6">
<input type="email" class="form-control" placeholder="Last Name" ng-model="formData.lastName">
</div>
</div>
<br>
<div class="alert alert-danger" role="alert">
<strong>Oh snap!</strong> Please enter your full name.
</div>
wizard.js
angular.module('dingocvWebApp')
.controller('WizardCtrl', function ($scope, stub) {
// Wizard methods
var vm = this;
vm.currentStep = 1;
vm.formData = {};
vm.steps = [
{
step: 1,
name: 'Name',
template: 'views/wizard/step1.html'
},
{
step: 2,
name: 'Email',
template: 'views/wizard/step2.html'
},
{
step: 3,
name: 'Job Category',
template: 'views/wizard/step3.html'
},
];
vm.gotoStep = function(newStep) {
vm.currentStep = newStep;
console.log(vm.formData.firstName);
};
vm.getStepTemplate = function(){
for (var i = 0; i < vm.steps.length; i++) {
if (vm.currentStep === vm.steps[i].step) {
return vm.steps[i].template;
}
}
};
// Step 1
// Step 2
// Step 3
$scope.jobCategories = stub.getJobCategories();
// Yeoman defaults
this.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
});

I got it working. These were the tweaks:
script.js
I declared the formData object so that its interface is readily visible for us humans:
//Model
vm.currentStep = 1;
vm.formData = {firstName: null, lastName: null};
Each step gained a isReady() function that inspects the state of the vm object to decide if the user can interact with that step:
vm.steps = [
{
step: 1,
name: "First step",
template: "step1.html",
isReady: function() { return true; }
},
{
step: 2,
name: "Second step",
template: "step2.html",
isReady: function() { return vm.formData.firstName && vm.formData.lastName; }
},
{
step: 3,
name: "Third step",
template: "step3.html",
isReady: function() { return true; } // Didn't really care to write this one, sorry :)
},
];
Then, a vm.canGoForward() method was introduced. It checks for the readiness (and existence) of the next step in the chain:
vm.canGoForward = function() {
var res = true,
i,
nextStateIndex = vm.currentStep + 1;
if (nextStateIndex > vm.steps.length) {
return false;
}
for (i = 1; res && i <= nextStateIndex; i++) {
res = (res && vm.steps[i-1].isReady());
}
return !!res;
}
(If the above code looks a bit confusing, it might be so because of the 1-index baseness of the currentStep member; I'd prefer it to be 0-based...)
step1.html
The textboxes should indeed have the "vm." object identifier prepended to the ng-model values. This instructs the angular engine to get/set the appropriate values.
index.html
The forward button was changed so the ng-disabled directive would behave accordingly to the vm.canGoForward() function:
<button ng-disabled="!vm.canGoForward()" class="btn btn-primary" name="next" type="button" ng-click="vm.gotoStep(vm.currentStep + 1)">Next step <i class="fa fa-arrow-right"></i></button>

Related

Angular: Showing only checked items in a checkbox list

Is it possible to show only a list of checked items in a checkbox list?
What I want to do is select a few items on a checked list and when I press "Show only checked items", I want to toggle between showing only the checked items in the checkbox list and showing the entire list with the checked items.
I searched angular's site but wasn't able to find a solution to it.
Fiddle: http://jsfiddle.net/fjoLy5sq/422/
<div ng-controller="DemoCtrl">
<label ng-repeat="role in roles">
<input type="checkbox" checklist-model="user.roles" checklist-value="role.id"> {{role.text}}
</label>
<br>
<button ng-click="checkAll()">check all</button>
<button ng-click="uncheckAll()">uncheck all</button>
<button ng-click="checkFirst()">check first</button>
<button ng-click="checkFirst()">Show only Checked</button>
<br><br>
user.roles {{ user.roles | json }}
</div>
Javascript:
angular.module("DemoApp", ["checklist-model"])
.controller('DemoCtrl', function($scope) {
$scope.roles = [
{id: 1, text: 'guest'},
{id: 2, text: 'user'},
{id: 3, text: 'customer'},
{id: 4, text: 'admin'}
];
$scope.user = {
roles: [2, 4]
};
$scope.checkAll = function() {
$scope.user.roles = $scope.roles.map(function(item) { return item.id; });
};
$scope.uncheckAll = function() {
$scope.user.roles = [];
};
$scope.checkFirst = function() {
$scope.user.roles.splice(0, $scope.user.roles.length);
$scope.user.roles.push(1);
};
});
Add a new variable in controller:
$scope.showAll = true;
In a view inverse the value of showAll when Show only Checked button is clicked:
<button ng-click="showAll = !showAll">Show only Checked</button>
To show only checked items, use Array.includes method, and check that current role is in user.roles:
<label ng-repeat="role in roles" ng-if="user.roles.includes(role.id)">
<input type="checkbox" checklist-model="user.roles" checklist-value="role.id"> {{role.text}}
</label>
Working demo

How do I properly submit an object into an array using Angular Js

I'm trying to learn AngularJs and writing some throw away code. I'm trying to create an object Bookmark and push it into an array.
HTML:
<h2>Create a new bookmark </h2>
<form class="form-group" ng-submit="createBookmark(newBookmark)" novalidate>
<!--Title-->
<h4>Bookmark Title</h4>
<input type="text" ng-model="newBookmark.title">
<!--Url-->
<h4>Bookmark Url</h4>
<input type="text" ng-model="newBookmark.url">
<!--Submit-->
<button type="submit" href="#" class="btn btn-primary" id="crForm" ng-click="stopCreating()">Save</button>
</form>
JS:
function resetCreateForm(){
$scope.newBookmark = {
title : '',
url : '',
category : $scope.currentCategory.name
};
}
function createBookmark(bookmark) {
bookmark.id = $scope.bookmarks.length;
bookmark.category = $scope.currentCategory.name;
$scope.bookmarks.push(bookmark);
resetCreateForm();
}
$scope.createBookmark = createBookmark;
$scope.resetCreateForm = resetCreateForm;
Object:
$scope.bookmarks = [
{id: 0, title: "Title1", url: "www.Title1.com", category: "Development"},
{id: 1, title: "Title2", url: "www.Title2.com", category: "Development"}
];
Module and Controller:
var app = angular.module('list',[]);
app.controller('listController', function($scope){
For some reason it does not work, so far I think it's from changes in the Angular version but I could not find a way to make it work.
You have to bind resetCreateForm & createBookmark in $scope of controller so that you can access them from view.
//place this inside your controller
$scope.resetCreateForm = resetCreateForm;
$scope.createBookmark= createBookmark;
Also you don't need to call function on ng-click, on click of button ng-submit will get called. Remove ng-click="stopCreating()"

Angular UI Bootstrap datepicker switching to today on open

Background
I'm adding a date, with Angular UI Bootstrap support, similar like I have implemented in this Plunk. The Plunk does exactly what I want it to do.
However, on my local attempt, the selected date switches to today, rather than the date set in ng-model.
Local code
<p class="input-group">
<input type="text" class="form-control"
uib-datepicker-popup="{{format}}"
ng-model="person.DateOfBirth"
is-open="stats.DateOfBirthOpened" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="openDatepicker($event)">
<i class="fa fa-calendar"></i>
</button>
</span>
</p>
Script:
$scope.format = "dd/MM/yyyy";
$scope.stats = {
DateOfBirthOpened: false
};
$scope.openDatepicker = function ($event) {
$scope.stats.DateOfBirthOpened = true;
};
Plunk code
<div class="input-group">
<input type="text" class="form-control"
uib-datepicker-popup="{{format}}"
ng-model="today"
is-open="status.opened"
min-date="minDate"
max-date="maxDate"
datepicker-options="dateOptions"
ng-required="true"
close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)">
<i class="fa fa-calendar"></i>
</button>
</span>
</div>
Script:
$scope.status = {
opened: false
};
$scope.open = function ($event) {
$scope.status.opened = true;
};
$scope.dateFormat = "dd/mm/yyyy";
Question
As far as I can tell both implementations are very similar (identical ignoring semantical differences), so why is the local code not working correctly (displaying today on open rather than ng-model value)?
Addendum
Full controller javascript code as requested:
"use strict";
angular.module("controllers.mainControllers")
.controller("personDetailController", [
"$scope",
function ($scope) {
var self = {};
self.Get = function () {
if (coreUtilityService.IsValid($rootScope.PersoonId)) {
apiService.Get("api/person/" + $rootScope.PersoonId).then(function (person) {
$scope.person = person;
$scope.title = coreUtilityService.CombineTwoValuesWithSpace(person.Firstname, person.Lastname);
});
};
}
// === $SCOPED === //
self.Get();
$scope.format = "dd/MM/yyyy";
$scope.stats = {
DateOfBirthOpened: false
};
$scope.openDatepicker = function ($event) {
$scope.stats.DateOfBirthOpened = true;
};
}]);
And $scope.person output:
{PersonId: "9245fe4a-d402-451c-b9ed-9c1a04247482", Firstname: "Jackie", Lastname: "Chan", DateOfBirth: "1968-05-17T00:00:00", CreateDate: "2015-12-11T09:15:49.403"…}
CreateDate: "2015-12-11T09:15:49.403"
DateOfBirth: "1968-05-17T00:00:00"
Firstname: "Jackie"
Lastname: "Chan"
ModifyDate: "2015-12-11T09:15:49.403"
PersonId: "9245fe4a-d402-451c-b9ed-9c1a04247482"
The date format in your plunk is wrong use "{{dd/MM/yyyy}}" instead of "{{dd/mm/yyyy}}".
EDIT: Also the plunk example does not bind the format correctly. Change {{format}} to {{dateFormat}}.

KnockoutJS not binding

I cant seem to get the binding to work on my KnockoutJS app.
JSFIDDLE -> http://jsfiddle.net/maylortaylor/pfqnkj17/
Here is the format of my JSON (generated by using <pre data-bind="text: ko.toJSON($root.forms,null,2)"></pre>)
[
{
"formTitle": "formTitle",
"formDescription": "formDesc",
"fieldTemplates": [
{
"fieldId": "text1",
"title": "title",
"description": "description fieldTemplate",
"isReq": true
},
{
"fieldId": "text2",
"title": "ttitle22",
"description": "description fieldTemplate 2",
"isReq": false
}
]
}
]
And here is how i am trying to call it in the page
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" placeholder="Title" class="span8 hideOffFocus input-full large-type">
<input data-bind="textInput: formDescription" type="text" placeholder="Description" class="hideOffFocus input-full">
</div
neither of those bindings work.
I create the forms object here
var FormModel = function (forms) {
var self = this;
self.forms = ko.observableArray(ko.utils.arrayMap(forms, function (form) {
return {
formTitle: form.formTitle, formDescription: form.formDescription,
fieldTemplates: ko.observableArray(form.fieldTemplates) };
}));
};
ko.applyBindings(new FormModel(initialData));
i hope your are expecting something like this
Working fiddle here
Now if you change something in textboxes in preview you can see automatic updates i.e mapping does make things back to ko way .
View Model :
var mapping = {
'fieldTemplates': {
create: function (options) {
return new FormModel(options.data);
}
}
}
function FormModel(forms) {
var self = this;
self.forms = ko.observableArray();
ko.mapping.fromJS(forms, mapping, self);
// other stuff
}
View :
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" />
<input data-bind="textInput: $root.formDescription" type="text"/><br/>
<div data-bind="foreach:$root.fieldTemplates">
<span data-bind="text:fieldId"></span>
<span data-bind="text:title"></span>
<span data-bind="text:description"></span>
<span data-bind="text:isReq"></span>
<br/>
</div>
</div>

Ember.js quiz questions: save/delete hasMany data

I'm building a quiz generator as my first Ember project, but I'm struggling. I've been able to create, edit and delete quizzes and save them to localstorage, but I'm having trouble saving/deleting quiz questions for each quiz.
I'm building it in Yeoman-Ember. I tried to add a demo to JSBin but it didn't work, so I've got a demo build here:
http://test.richardwestenra.com/embertest/
and here's a zip containing the current state of the build: http://test.richardwestenra.com/embertest/dev.zip
Here's my combined-scripts.js file:
(function() {
var Quizmaker = window.Quizmaker = Ember.Application.create();
/* Order and include as you please. */
})();
(function() {
/* global $ */
Quizmaker.QuizzesController = Ember.ObjectController.extend({
});
Quizmaker.NewController = Ember.ObjectController.extend({
content: {},
quiztypes: ['Multiple choice', 'List', 'Either/or'],
actions: {
save: function(){
var title = $('#title').val();
var excerpt = $('#excerpt').val();
var quiztype = $('#quiztype').val();
var fullname = $('#fullname').val();
var submittedOn = new Date();
var store = this.get('store');
if (Ember.isEmpty(title)) {
window.alert('Please enter a title');
return false;
} else if (Ember.isEmpty(quiztype)) {
window.alert('Please enter a quiz type');
return false;
}
var quiz = store.createRecord('quiz',{
quiztype : quiztype,
fullname : fullname,
title : title,
excerpt : excerpt,
submittedOn : submittedOn
});
quiz.save();
this.transitionToRoute('index');
},
cancel: function(){
this.transitionToRoute('index');
},
createQuestion: function(){
window.alert('This doesn\'t work for new questions. I don\'t know why. It works for existing questions.');
var store = this.get('store');
var question = store.createRecord('question',{
question : 'Test question ' + new Date()
});
var model = this.get('model');
var questions = this.get('questions');
questions.pushObject(question);
model.set('questions', questions);
model.save();
}
}
});
Quizmaker.QuizController = Ember.ObjectController.extend({
quiztypes: ['Multiple choice', 'Checklist', 'Boolean'],
actions: {
edit: function(){
this.transitionToRoute('quiz.edit');
this.set('isEditing', true);
},
doneEditing: function(){
var model = this.get('model');
var title = $('#title').val();
var excerpt = $('#excerpt').val();
var quiztype = $('#quiztype').val();
var fullname = $('#fullname').val();
var questions = this.get('questions');
if (Ember.isEmpty(title)) {
window.alert('Please enter a title');
return false;
} else if (Ember.isEmpty(quiztype)) {
window.alert('Please enter a quiz type');
return false;
} else {
this.set('isEditing', false);
model.set('title', title);
model.set('excerpt', excerpt);
model.set('quiztype', quiztype);
model.set('fullname', fullname);
model.set('questions', questions);
model.save();
this.transitionToRoute('quiz');
}
},
cancel: function(){
if (window.confirm('Are you sure you want to abandon your changes?')){
this.set('isEditing', false);
this.transitionToRoute('quiz');
}
},
remove: function(){
if (window.confirm('Are you sure you want to delete this quiz?')){
var quiz = this.get('model');
quiz.destroyRecord();
this.transitionToRoute('index');
}
},
createQuestion: function(){
var store = this.get('store');
var question = store.createRecord('question',{
question : 'Test question ' + new Date()
});
var model = this.get('model');
var questions = this.get('questions');
questions.pushObject(question);
model.set('questions', questions);
model.save();
}
}
});
Quizmaker.QuestionsController = Ember.ArrayController.extend({
needs: 'quiz',
quiz: Ember.computed.alias("controllers.quiz"),
actions: {
createQuestion: function(){
var store = this.get('store');
var question = store.createRecord('question',{
question : 'Test question ' + new Date()
});
var quiz = this.get('quiz');
var questions = quiz.get('questions');
questions.pushObject(question);
console.log(question);
},
removeQuestion: function(id){
var question = this.findProperty('id', id);
this.removeObject(question);
}
}
});
})();
(function() {
Quizmaker.Store = DS.Store.extend();
// Quizmaker.ApplicationAdapter = DS.FixtureAdapter;
Quizmaker.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'quizzes'
});
})();
(function() {
/* global Ember */
Quizmaker.Quiz = DS.Model.extend({
title : DS.attr('string'),
excerpt : DS.attr('string'),
fullname : DS.attr('string'),
quiztype : DS.attr('string'),
questions: DS.hasMany('question', {async: true}), // via http://stackoverflow.com/questions/22494140/in-ember-js-how-do-i-create-a-computed-property-that-references-first-item-in-pr
questionsCount: function() {
return this.get('questions.length');
}.property('questions.#each'), // via http://stackoverflow.com/questions/16463958/how-to-use-multiple-models-with-a-single-route-in-emberjs-ember-data
// firstQuestion: function() {
// return this.get('questions.firstObject');
// }.property('questions.firstObject')
submittedOn : DS.attr('date')
});
Quizmaker.Question = DS.Model.extend({
quiz: DS.belongsTo('quiz'),
question: DS.attr('string'),
answers: DS.attr('string')
});
// delete below here if you do not want fixtures
Quizmaker.Quiz.FIXTURES = [
{
id: 0,
title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
excerpt: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.',
quiztype: 'Boolean',
fullname: 'Full Name',
submittedOn: null
}
];
Quizmaker.Question.FIXTURES = [
{
id: 0,
question: 'Test question Lorem ipsum dolor sit amet, consectetur adipiscing elit',
quiz: 0,
answers: [
{ answer: 'alpha', weight: 0 },
{ answer: 'beta', weight: 5 }
]
}
];
})();
(function() {
Quizmaker.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.get('store').findAll('quiz');
}
});
Quizmaker.QuizzesRoute = Ember.Route.extend({
model: function() {
return this.get('store').find('quiz');
}
});
Quizmaker.QuizRoute = Ember.Route.extend({
model: function(params) {
return this.get('store').find('quiz', params.quiz_id);
}
});
Quizmaker.QuestionsRoute = Ember.Route.extend({
controllerName: 'quiz',
model: function() {
return this.store.findAll('question');
}
});
})();
(function() {
Quizmaker.QuizView = Ember.View.extend({
keyDown: function(e) {
var esc = 27;
if(e.keyCode === esc){
this.get('controller').send('cancel');
}
}
});
// Give the text fields one way value binding so they don't automatically update
Quizmaker.TextField = Ember.TextField.extend({
valueBinding: Ember.Binding.oneWay('source')
});
Quizmaker.TextArea = Ember.TextArea.extend({
valueBinding: Ember.Binding.oneWay('source')
});
Quizmaker.Select = Ember.Select.extend({
valueBinding: Ember.Binding.oneWay('source')
});
})();
(function() {
Quizmaker.QuizzesView = Ember.View.extend({
});
})();
(function() {
Quizmaker.Router.map(function () {
// Add your routes here
this.resource('index',{path : '/'});
this.resource('new' , {path : '/quiz/new'});
this.resource('quizzes' , {path : '/quizzes'});
this.resource('questions' , {path : '/questions'});
this.resource('quiz', { path: '/quiz/:quiz_id' }, function(){
this.route('edit', { path: '/edit' });
});
});
})();
(function() {
/* global moment */
Ember.Handlebars.helper('format-date', function(date){
return moment(date).fromNow();
});
})();
This is quiz.hbs:
{{#if isEditing}}
<h1>Edit quiz</h1>
{{partial "editform"}}
{{else}}
<h1>{{title}}</h1>
<h4>A <em style="text-transform: lowercase;">{{quiztype}}</em> quiz by {{fullname}} <small class="muted">{{format-date submittedOn}}</small></h4>
<hr>
<p class="lead">{{excerpt}}</p>
<button type="submit" class="btn btn-default" {{action 'edit'}}>Edit</button>
<button class="btn btn-danger pull-right" {{action 'remove'}}>Delete Quiz</button>
{{/if}}
This is _editform.hbs:
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">Title</label>
<div class="col-sm-10">
{{!-- {{#view Quizmaker.TextInputView}}
{{/view}} --}}
{{!-- <input type="text" class="form-control" id="title" name="title" placeholder="Title of the quiz" required> --}}
{{view Quizmaker.TextField type="text" class="form-control" id="title" name="title" sourceBinding="title" placeholder="Title of the quiz" required="required" }}
</div>
</div>
<div class="form-group">
<label for="excerpt" class="col-sm-2 control-label">Excerpt</label>
<div class="col-sm-10">
{{!-- <textarea class="form-control" id="excerpt" name="excerpt" placeholder="Short description of the quiz" required></textarea> --}}
{{view Quizmaker.TextArea class="form-control" id="excerpt" name="excerpt" sourceBinding="excerpt" placeholder="Short description of the quiz" rows="3" required="required" }}
</div>
</div>
<div class="form-group">
<label for="fullname" class="col-sm-2 control-label">Author</label>
<div class="col-sm-10">
{{!-- <input type="text" class="form-control" id="fullname" name="fullname" placeholder="Enter your Full Name like Alan Smithee" required> --}}
{{view Quizmaker.TextField type="text" class="form-control" id="fullname" name="fullname" sourceBinding="fullname" placeholder="Enter your full name, e.g. Alan Smithee" required="required" }}
</div>
</div>
<div class="form-group">
<label for="quiztype" class="col-sm-2 control-label">Quiz type</label>
<div class="col-sm-10">
{{view Quizmaker.Select id="quiztype" name="quiztype" class="form-control" viewName="select" content=quiztypes prompt="Pick a type:" sourceBinding="quiztype"}}
</div>
</div>
<div class="form-group">
<label for="quiztype" class="col-sm-2 control-label">Questions ({{questionsCount}})</label>
<div class="col-sm-10">
{{render "questions" questions}}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
{{#if isEditing}}
<button type="submit" class="btn btn-success" {{action 'doneEditing'}}>Save</button>
<button class="btn btn-warning pull-right" {{action 'cancel'}}>Cancel</button>
{{else}}
<button type="submit" class="btn btn-success" {{action 'save'}}>Save</button>
<button class="btn btn-warning pull-right" {{action 'cancel'}}>Cancel</button>
{{/if}}
</div>
</div>
</form>
And this is questions.hbs:
<p><button class="btn btn-info btn-sm" {{action 'createQuestion'}}><span class="glyphicon glyphicon-plus"></span> Add new question</button></p>
<table class="table table-striped">
{{#each model}}
<tr>
<td>{{this.question}}</td>
<td>{{this.id}}</td>
<td><button class="btn btn-danger btn-xs pull-right" {{action 'removeQuestion' id}}>Delete</button></td>
</tr>
{{/each}}
</table>
If anyone can point out ways I can improve it, I'd be very grateful. I've tried implementing every method I can find to get it to save or delete hasMany data but it always throws different errors and I'm not really sure where to proceed from here.
I'm also trying to figure out how to toggle the isEditing state depending on whether the /edit route is displayed (instead of having it activated by an action).
let's break this down into two different items, last to first.
The trick in showing the edit mode versus view mode is to take advantage of the free index route which is rendered at the root of every resource.
You can change your quiz template to be an outlet
{{outlet}}
and create a quiz/index template which originally held your quiz data. This template will only show up when you are at /quiz/123 when you navigate to /quiz/123/edit the index template will be replaced with the edit template. (You'll probably want to return the model from the resource to both routes using modelFor).
App.Router.map(function() {
this.resource('foo', {path:'/'},function(){
this.route('edit');
})
});
App.FooRoute = Em.Route.extend({
model: function() {
return [{color:'red'}, {color:'yellow'},{color: 'blue'}];
}
});
App.FooIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('foo');
}
});
App.FooEditRoute = Ember.Route.extend({
model: function() {
return this.modelFor('foo');
}
});
http://emberjs.jsbin.com/lodeviyu/1/edit
In Ember Data if you have two record types that each have a relation to each other:
App.FooRecord = DS.Record.extend({
bars: DS.hasMany('bar')
});
App.BarRecord = DS.Record.extend({
foo: DS.belongsTo('foo')
});
Let's create and associate two records:
var foo = store.create('foo');
var bar = store.create('bar');
foo.get('bars').pushObject(bar);
bar.set('foo', foo);
Now let's save, and see what Ember Data will do:
foo.save();
// json sent
{
foo: {}
}
// responds with id 1
bar.save();
// json sent
{
bar: {
foo: 1
}
}
Ember Data chooses not to save the hasMany relationship if their is a correlated belongsTo from a different model type.
https://github.com/emberjs/data/commit/7f752ad15eb9b9454e3da3f4e0b8c487cdc70ff0#commitcomment-6078838

Categories

Resources