I am using Marionette to try an render a list of questions. Each question can have a select list of question types that will also come from the server. I would prefer to keep this list in the top level of the json instead of on each question. Is there any way to loop over the questions in the ItemView, yet still have access to the question types in the template?
I can do this as one large template that gets re-rendered each time, but would prefer to avoid that.
JSON:
var json = {
questions: [
{ id: 1, questionType: "FreeText", label: "What is your name?" }
],
questionTypes: [
{ label: "Free Text", qtype: "FreeText" },
{ label: "Single Select", qtype: "Select" },
{ label: "Multi Select", qtype: "MultiSelect" }
]
};
Template:
{{#each questions}}
<div class="question-editor">
<button class="remove-question" data-id="{{this.id}}">X</button>
<label for="QuestionType">Question Type:</label>
<select name="QuestionType">
{{#each ../questionTypes}}
<option value="{{this.qtype}}" {{#isOptionSelected ../questionType this.qtype}} selected="selected" {{/isOptionSelected}}>{{this.label}}</option>
{{/each}}
</select>
<br />
<label for="Label">Label:</label>
<input type="text" name="Label" value="{{this.label}}" />
</div>
{{/each}}
you can use marionette's CompositeView.
from marionette documentation:
ItemView = Backbone.Marionette.ItemView({
initialize: function(options){
console.log(options.foo); // => "bar"
}
});
CollectionView = Backbone.Marionette.CompositeView({
itemView: ItemView,
itemViewOptions: {
foo: "bar"
}
});
you can pass the questionTypes as itemViewOptions for each row
Related
I have json data array of Carpet objects (see plunker)
designerApp.controller('CarpetCtrl', ['$scope', function ($scope) {
$scope.carpetList =
[
{
"Carpet": {
"name": "red carpet",
"standard": true,
"wide": false,
"extra_wide": true
},
},
{
"Carpet": {
"name": "blue carpet",
"standard": false,
"wide": true,
"extra_wide": true
},
},
{
"Carpet": {
"name": "green carpet",
"standard": true,
"wide": true,
"extra_wide": false
},
}
];
}]);
I want to use radio buttons to filter the list - buttons for standard, wide and extra_wide to filter the respective boolean values.
I have scoured the internet and cannot find anyone else doing this. Can angular handle this natively?
NB The reason each array element is keyed on Carpet is because I have cut down the data pumped out from CakePHP - there is other keyed data in each element.
Here's a working Plunker.
This is what I changed:
<div class="carpet_list" ng-controller="CarpetCtrl">
<label>Standard Width<input type="radio" name="width" ng-model="type" value='standard' ></label>
<label>Wide<input type="radio" name="width" ng-model="type" value='wide' ></label>
<label>Extra Wide<input type="radio" name="width" ng-model="type" value='extra_wide'></label>
<br>
<br>
{{type}}
<h2>Carpet List</h2>
<ul class="thumblist">
<li class="thumb" ng-repeat="item in carpetList">
<label ng-show="item.Carpet[type]">{{item.Carpet.name}}</label>
</li>
</ul>
</div>
As you can see the idea is to show the carpets with 'type' true.
Here's a way: plunker
The radios model is a single variable containing the type of carpet to display
$scope.filters = {
width: 'standard'
};
Then you display only the element on which this property is true.
<li class="thumb" ng-repeat="item in carpetList" ng-show="item.Carpet[filters.width]">
There's probably a "cleaner" way with filters, but apparently you can't filter a single item, you have to filter the whole array
if you have an array of items, without properties, how do you access the value inside a for loop?
I currently get the right number of options, but I haven't found the correct syntax to get the value of the option.
there's a working jsfiddle at: http://jsfiddle.net/geewhizbang/Y44Gm/4/
var data = {
items: [{
"title": "First Drop Down",
"hist": "Secondary",
"dec": "Priority",
"options": ["Priority", "Secondary"],
"type": "select"
}, {
"title": "Second Drop Down",
"hist": "Competitive Widget",
"dec": "Competitive Widget",
"options": ["Yadda", "Badda", "Bing", "Mobile", "Server", "Client", "Snickerdoodle"],
"type": "select"
}]
};
$.views.converters("dateFormat", function (val) {
if (val == null) return "";
var d = new Date(val);
if (d.getFullYear() == "1900") return "";
return d.getMonth() + "/" + d.getDate() + "/" + d.getFullYear();
});
$template = $.templates("#template");
$("#container").html($template.render(data));
the body of this, including template:
<div id="container">
<script id="template" type="text/x-jsrender">
{{for items}}
<div class="bodyItem">
<div class="colDec">
<p>{{>title}}</p>
{{if type == "select"}}
<select data-link="{{>dec}}">
{^{for options}}
<option value="{{#}}">{#}</option>
{{/for}}
</select>
{{else}}
{{if type == "date"}}
<input value="{{dateFormat:dec}}" class="date" />
{{else}}
<div contentEditable="true">{{>dec}}</div>
{{/if}}
{{/if}}
</div>
<div class="colHist">
<p>{{>title}}</p>
{{if type == "date"}}
<input value="{{dateFormat:dec}}" class="date" />
{{else}}
<div>{{>dec}}</div>
{{/if}}
</div>
</div>
{{/for}}
</script>
If options is an array of strings, you need:
<select data-link="dec">
{^{for options}}
<option value="{{:#data}}">{{:#data}}</option>
{{/for}}
</select>
Note also the data-link expression on the select element.
In general, in your jsfiddle, you can use data-linking much more. For example:
<div>{^{>dec}}</div>
(Note the ^)
And to do data-linking you need
$template.link("#container", data);
rather than just calling render...
There are more changes needed, but here is an update of your jsfiddle which does select binding: http://jsfiddle.net/BorisMoore/Y44Gm/5/
I have a following data structure coming from REST:
scope.taglist =
[ { name: "mylist", tags: ["tag1", "tag2", "tag3", ...]}, { name:
"mylist2", tags: ["tag2.1", "tag2.2", "tag2.3", ...]} ]
In order to present the names of the objects I have the following html:
<div>
<select ng-model="tagNameSelection">
<option ng-repeat="tagObj in taglist" value="{{tagObj}}">{{tagObj.name}}</option>
</select>
</div>
<div class="tagdetails">
<!-- present the list of tags from tagNameSelection -->
</div>
Now I am a little bit of a loss on how to present the tags list of
individual object. I am able to present the array in raw format (by
sticking {{tagNameSelection}} inside the tagdetails div) but when I
try to iterate through those with ng-repeat angular gives a error
message.
Oddly enough when I hard-code one of the tag lists to the scope in controller the ng-repeat works flawlessly.
Maybe you interesting something like this:
HTML
<div ng-controller="fessCntrl">
<div>
<select ng-model="tagNameSelection"
ng-options="tagObj as tagObj.name for tagObj in taglist"
ng-change="change(tagNameSelection)"></select>
</div>
<pre>{{tagNameSelection.tags|json}}</pre>
<div class="tagdetails">
<ul ng-repeat="tag in tagNameSelection.tags">
<li>{{tag}}</li>
</ul>
</div>
</div>
Controller
var fessmodule = angular.module('myModule', []);
fessmodule.controller('fessCntrl', function ($scope) {
$scope.change = function (value) {
};
$scope.taglist = [{
name: "mylist",
tags: ["tag1", "tag2", "tag3"]
}, {
name: "mylist2",
tags: ["tag2.1", "tag2.2", "tag2.3"]
}]
});
fessmodule.$inject = ['$scope'];
See Fiddle
I am using angular-ui's sortable-ui module and am trying to raise a cancel so that the dragged items returns to it original location in the source list. Unfortunately I cannot get this working. Here is an example:
http://jsfiddle.net/Ej99f/1/
var myapp = angular.module('myapp', ['ui.sortable']);
myapp.controller('controller', function ($scope) {
$scope.list = ["1", "2", "3", "4", "5", "6"];
$scope.list2 = ["7", "8", "9"];
$scope.sortableOptions = {
update: function(e, ui) {
if (Number(ui.item.text()) === 6) {
ui.item.parent().sortable('cancel');
}
},
receive: function(e, ui) {
ui.sender.sortable('cancel');
ui.item.parent().sortable('cancel');
},
connectWith: ".group",
axis: 'y'
};
});
angular.bootstrap(document, ['myapp']);
Any help would be gratefully appreciated.
well, when it comes to angular, all roads lead to the data "the single source of truth". So update your model back to it's original state, before the move, and you're all set :)
example below has two lists, the first one being restricted for
its sorting (the update method)
and for sending an item (receive method on list 2)
the second list you can sort, and send items to list 1
(using foundation4 for css)
<div ng-app="test">
<div ng-controller="sortableTest">
<div class="small-4 columns panel">
<ul data-drop="true"
ui-sortable="sortable.options.list1" ng-model="sortable.model.list1">
<li ng-repeat="fruit in sortable.model.list1"
data-id="{{ fruit.id }}">{{ fruit.label }}</li>
</ul>
</div>
<div class="small-4 columns panel">
<ul data-drop="true"
ui-sortable="sortable.options.list2" ng-model="sortable.model.list2">
<li ng-repeat="element in sortable.model.list2"
data-id="{{ element.id }}">{{ element.label }}</li>
</ul>
</div>
<div class="clear"></div>
<br />
<span ng-repeat="fruit in sortable.model.list1">{{ fruit.label }} </span><br />
<span ng-repeat="element in sortable.model.list2">{{ element.label }} </span><br />
<span ng-repeat="fruit in sortable.oldData.list1">{{ fruit.label }} </span><br />
<span ng-repeat="element in sortable.oldData.list2">{{ element.label }} </span><br />
</div>
</div>
js:
var test = angular.module('test', ['ui.sortable']);
test.controller('sortableTest', function($scope, $timeout) {
$scope.sortable = {
model: {
list1: [{id: 1, label: 'apple'},{id: 2, label: 'orange'},{id: 3, label: 'pear'},{id: 4, label: 'banana'}],
list2: [{id: 5, label: 'earth'},{id: 6, label: 'wind'},{id: 7, label: 'fire'},{id: 8, label: 'water'}]
},
oldData: {
list1: [],
list2: []
},
options: {
list1: {
update: function(event, ui) {
console.debug('up-list1');
$scope.sortable.oldData.list1 = $scope.sortable.model.list1.slice(0);
$scope.sortable.oldData.list2 = $scope.sortable.model.list2.slice(0);
// DO NOT USE THIS! it messes up the data.
// ui.item.parent().sortable('cancel'); // <--- BUGGY!
// uncomment and check the span repeats..
$timeout(function(){
$scope.sortable.model.list1 = $scope.sortable.oldData.list1;
$scope.sortable.model.list2 = $scope.sortable.oldData.list2;
});
},
connectWith: 'ul'
},
list2: {
update: function(event, ui) {
console.debug('up-list2');
},
connectWith: 'ul',
receive: function(event, ui) {
console.debug('re-list2');
$timeout(function(){
$scope.sortable.model.list1 = $scope.sortable.oldData.list1;
$scope.sortable.model.list2 = $scope.sortable.oldData.list2;
});
}
}
}
};
});
you can of course use a service or something to store the old value. One can use ui.sender to differentiate the senders, if you have more that two..
I have an array of objects with arrays of objects in it:
var content = [
{
name: 'Foo',
sub: [{ name: 'Bar' }, { name: 'Foobar' }]
},
...
]
and a template:
<input ng-model="search" />
<div ng-repeat="item in content | filter:search>
{{item.name}}
<div ng-repeat="key in item">
{{key.name}}
</div>
</div>
Now, I use the filter filter to search for string matches, but it applies only to the first ng-repeat directive. How could I include the second directive into the search filter? Thanks in advance.
You can simply apply a second filter expression to your other ng-repeat
<div ng-repeat="key in item | filter:secondFilterExpression">
See docs here http://docs.angularjs.org/api/ng.filter:filter
Ok, I've 'flattened' the object to a simple collection before passing it through the controller to the template, so I don't have to worry about a second ng-repeat cycle. The output is
[{ name : 'foo' }, { name : 'bar' }, { name : 'foobar' }]