I have a wrapping formatting vue component with a slot, and two components that use it. When passing a list structure it works fine, as it does when using the wrapping div directly, but it fails when trying to pass a simple string. The first word of the string renders as expected, but the rest appears in the opening div. I've tried wrapping the mustached prop in additional html tags, but the content still doesn't render correctly.
Display Text component
<template>
<two-col-row-display
:field="field"
:fieldcss="fieldcss"
:valuecss="valuecss"
>
{{ value }}
</two-col-row-display>
</template>
<script>
export default {
props: {
field: {
type: String,
required: true,
},
fieldcss: {
type: String,
},
value: {
type: String,
default: '',
},
valuecss: {
type: String,
},
},
}
</script>
Two column row display wrapper component
<template>
<div class="gridwrap">
<div class="row field" :class="fieldcss">
{{ field + ':' }}
</div>
<div class="row value" :class="valuecss">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
field: {
type: String,
},
fieldcss: {
type: String,
},
valuecss: {
type: String,
},
},
}
</script>
Display Text used
<display-text
field="Description"
value={{ $recipe->description }}
></display-text>
But it outputs only the first part of the description "All" where it should. The rest of the description "of the recipe" gets output as an attribute of the wrapping div.
Output html
<div class="gridwrap" of the recipe>
<div class="row field"> Description: </div>
<div class="row value"> All </div>
</div>
Using the wrapping component directly works as expected, but I don't want to use that component directly.
<two-col-row-display
field="Description???"
>{{ $recipe->description }}
</two-col-row-display>
outputs the desired result for the display-text component
<div class="gridwrap">
<div class="row field"> Description???: </div>
<div class="row value">All of the recipe </div>
</div>
How can I modify the display-text component to render the text in the slot correctly?
I neglected to wrap the data passed to the value prop in quotes. The display text component should be filled out like:
<display-text
field="Description"
value="{{ $recipe->description }}"
></display-text>
I have a my-tag component that simply renders a title:
html
<div id="content"></div>
<script id="main-template" type="text/mustache">
<my-tag title="This is the title"></my-tag>
</script>
javascript
var Component = can.Component.extend({
tag: 'my-tag',
template: '<h1>{{title}}</h1>',
viewModel: {
title: '#'
}
});
$('#content').html(can.view('main-template', {}));
output
<div id="content">
<my-tag title="This is the title">
<h1>This is the title</h1>
</my-tag>
</div>
I would like to have the output as follows:
<div id="content">
<my-tag>
<h1>This is the title</h1>
</my-tag>
</div>
How can I get the component to not render the title attribute in my-tag?
Here is the jsfiddle.
You can't prevent it from rendering, however, you might be able to remove it after the component is created like:
var Component = can.Component.extend({
tag: 'my-tag',
template: '<h1>{{title}}</h1>',
viewModel: {
title: '#'
},
events: {
init: function(){
this.element.removeAttr("title");
}
}
});
Also, if you are starting a new CanJS project, I'd encourage you to switch to can.stache as that will be the default templating engine in 3.0. It's highly compatible with can.mustache.
I have a dojox/mvc/Repeat area which is bound to an array of records.
Within the row of the Repeat there is a field (the id of the record) which should be a simple display Output if the record has already been saved to the database, but it should be a TextBox if the record is new (the user must enter the value).
How do I solve this elegantly? I am fairly new to Dojo and its MVC part is very under-documented.
The most MVC-ish solution I have found so far is as follows:
1)
I put a "hasBeenSaved" property into the model which will mark the server-side saved state of the record. This attribute will be bound to the view with a transformation since the "display" style attribute of the DIV will be bound to the hasBeenSaved model attribute (one is a boolean the other is a string: "block"/"none").
2)
Within the Row, I put a conditionally visible div around the id input field. This will be visible only when the record is new, so its display style attribute is bound with an appropriate transformer attached to the Dojo MVC binding.
The same is done for the id output field but the transformer is different on the binding since this will be displayed only when the record has already been saved.
The JSFiddle which I have used to prototype this solution: http://jsfiddle.net/asoltesz/6t4dj1w7/15/
require([
"dojo/_base/declare", "dojo/dom-style", "dojo/parser", "dojo/ready",
"dijit/_WidgetBase", "dijit/_TemplatedMixin",
'dojox/mvc/getStateful'
], function(
declare, domStyle, parser, ready,
_WidgetBase, _TemplatedMixin,
getStateful
){
// setting up the data model for MVC
model = {
items: [
{ id: 'id1',
hasBeenSaved: true
},
{ id: null,
hasBeenSaved: false
},
{ id: null,
hasBeenSaved: false
},
{ id: 'id3',
hasBeenSaved: true
}
]
};
model = getStateful(model);
/**
* This mixin makes it possible to set the "display" style property of
* the DOM node (of any widget) as a Widget property and thus bind it to an MVC model
* when needed.
*/
declare("_DisplayAttributeMixin", [], {
// parameters
display: "block",
_setDisplayAttr: function(/*String*/ display){
this._set("display", display);
domStyle.set(this.domNode, "display", display);
}
});
/** Transformer methods for converting hasBeenSaved to visible/hidden values */
transfSavedToHidden = {format: function(hasBeenSaved){
console.log("transfSavedToHidden: " + (hasBeenSaved ? "none" : "block"));
return hasBeenSaved ? "none" : "block";
}};
transfSavedToVisible = {format: function(hasBeenSaved){
console.log("transfSavedToHidden: " + (hasBeenSaved ? "block" : "none"));
return hasBeenSaved ? "block" : "none";
}};
ready(function(){
// Call the parser manually so it runs after our mixin is defined, and page has finished loading
parser.parse();
});
});
The HTML markup:
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: model"
>
<div id="repeatId"
data-dojo-type="dojox/mvc/Repeat"
data-dojo-props="children: at('rel:', 'items')"
>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', ${this.index})"
>
<span>Record: ${this.index}</span>
<!-- This is displayed only when the record is new (not saved yet) -->
<div
data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="_DisplayAttributeMixin"
data-mvc-bindings="
display: at('rel:', 'hasBeenSaved')
.direction(at.from)
.transform(transfSavedToHidden)"
>
<label for="idInput${this.index}">id:</label>
<input
data-dojo-type="dijit/form/TextBox"
id="idInput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></input>
</div> <!-- end conditionally hidden div -->
<!-- This is displayed only when the record has already been saved -->
<div
data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="_DisplayAttributeMixin"
data-mvc-bindings="
display: at('rel:', 'hasBeenSaved')
.direction(at.from)
.transform(transfSavedToVisible)"
>
<label for="idInput${this.index}">id:</label>
<span
data-dojo-type="dojox/mvc/Output"
id="idOutput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></span>
</div> <!-- end conditionally hidden div -->
<hr/>
</div> <!-- end of row -->
</div> <!-- end of Repeat -->
</div> <!-- end of Group -->
A secondary, less complex solution:
Bind the "hasBeenSaved" property to a hidden text within the repeating div.
Put an onChange event on the hidden field which gets the index of the repeat as well.
The onChange event simply hides the field which is not appropriate in light of the hasBeenChanged property value for the record.
The fiddle is here: http://jsfiddle.net/asoltesz/8u9js6sz/5/
Code:
hasBeenSavedChanged = function(field, index) {
var divToHide
if (field.value == true) {
divToHide = "idInputDiv"
}
else {
divToHide = "idOutputDiv"
}
var div = document.getElementById(divToHide + index);
div.style.display = "none";
}
require([
"dojo/_base/declare", "dojo/dom-style", "dojo/parser", "dojo/ready",
"dijit/_WidgetBase", "dijit/_TemplatedMixin",
'dojox/mvc/getStateful'
], function(
declare, domStyle, parser, ready,
_WidgetBase, _TemplatedMixin,
getStateful
){
// setting up the data model for MVC
model = {
items: [
{ id: 'id1',
hasBeenSaved: true
},
{ id: null,
hasBeenSaved: false
},
{ id: null,
hasBeenSaved: false
},
{ id: 'id3',
hasBeenSaved: true
}
]
};
model = getStateful(model);
ready(function(){
// Call the parser manually so it runs after our mixin is defined, and page has finished loading
parser.parse();
});
});
HTML:
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: model"
>
<span id="itemsCtl"
data-dojo-type="dojox/mvc/ListController"
data-dojo-props="model: model.items">
</span>
<div id="itemsRepeat"
data-dojo-type="dojox/mvc/Repeat"
data-dojo-props="children: at('rel:', 'items')"
>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', ${this.index})"
>
<span>Record: ${this.index}</span>
<input
id="hasBeenChanged${this.index}"
data-dojo-type="dijit/form/TextBox"
data-dojo-props="value: at('rel:', 'hasBeenSaved')"
onChange="hasBeenSavedChanged(this, '${this.index}');"
type="hidden"
></input>
<!-- This is displayed only when the record is new (not saved yet) -->
<div id="idInputDiv${this.index}"
>
<label for="idInput${this.index}">id:</label>
<input
data-dojo-type="dijit/form/TextBox"
id="idInput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></input>
</div> <!-- end conditionally hidden div -->
<!-- This is displayed only when the record has already been saved -->
<div id="idOutputDiv${this.index}" >
<label for="idInput${this.index}">id:</label>
<span
data-dojo-type="dojox/mvc/Output"
id="idOutput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></span>
</div> <!-- end conditionally hidden div -->
<hr/>
</div> <!-- end of row -->
</div> <!-- end of Repeat -->
</div> <!-- end of Group -->
Mixin/transformer approach is something I had in mind, too. Two things I’d add there are out-of-the-box Dijit features, one is dijitDisplayNone class, another is attribute mapping feature.
Though it’s strange that the former is undocumented, the intent may have been for private usage within Dijit codebase.
Though it’s a bit hackish (and may be broken in future 1.x Dijit releases), overriding the Dijit code that’s responsible for attribute mapping will allow you to map a widget attribute to a CSS class that’s toggled.
Here’s a code sample that uses the above two:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/resources/dojo.css">
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dijit/themes/dijit.css">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/dojo.js" data-dojo-config="async: 1, parseOnLoad: 0"></script>
<script type="text/javascript">
require([
"dojo/_base/array",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/dom-class",
"dojo/parser",
"dojox/mvc/getStateful",
"dijit/form/TextBox"
], function(array, declare, lang, domClass, parser, getStateful){
declare("CssToggleMixin", null, {
// summary:
// Mixin class to support widget attributes with toggleClass type.
// toggleClass type allows boolean value of an attribute to reflect existence of a CSS class in a DOM node in the widget.
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
// summary:
// Handle widget attribute with toggleClass type.
// See dijit/_WidgetBase._attrToDom() for more details.
var callee = arguments.callee;
array.forEach((function(){ return lang.isArray(commands) ? commands.slice(0) : [commands]; })(arguments.length >= 3 ? commands : this.attributeMap[attr]), function(command){
command.type != "toggleClass" ?
this.inherited("_attrToDom", lang.mixin([attr, value, command], {callee: callee})) :
domClass.toggle(this[command.node || "domNode"], command.className || attr, value);
}, this);
}
});
flipConverter = {
format: function (value) {
return !value;
},
parse: function (value) {
return !value;
}
};
model = getStateful({
items: [
{
value: "Foo",
hasBeenSaved: true
},
{
hasBeenSaved: false
},
{
hasBeenSaved: false
},
{
value: "Bar",
hasBeenSaved: true
}
]
});
parser.parse();
})
</script>
</head>
<body>
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div data-dojo-type="dojox/mvc/WidgetList"
data-dojo-mixins="dojox/mvc/_InlineTemplateMixin"
data-dojo-props="children: at(model, 'items')">
<script type="dojox/mvc/InlineTemplate">
<div>
<span data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="CssToggleMixin"
data-dojo-props="value: at('rel:', 'value'),
noDisplay: at('rel:', 'hasBeenSaved').transform(flipConverter),
_setValueAttr: {node: 'domNode', type: 'innerText'},
_setNoDisplayAttr: {type: 'toggleClass', className: 'dijitDisplayNone'}"></span>
<span data-dojo-type="dijit/form/TextBox"
data-dojo-mixins="CssToggleMixin"
data-dojo-props="value: at('rel:', 'value'),
noDisplay: at('rel:', 'hasBeenSaved'),
_setNoDisplayAttr: {type: 'toggleClass', className: 'dijitDisplayNone'}"></span>
</div>
</script>
</div>
</body>
</html>
Hope it’ll shed some light.
Best, Akira
In this example I'm nesting radio buttons inside checkboxes:
html:
<div ng-app ng-controller="Controller">
<div name="myForm">
<ul>
<li ng-repeat="item in items">
<label>
<input type="checkbox" ng-model='item.selected' />{{item.name}}</label>
<ul>
<li ng-repeat="format in item.formats">
<label>
<input type="radio" ng-model='format.selected' name='what name ?'/>{{format.title}}</label>
</li>
</ul>
</li>
</ul>
</div>
<button ng-click='onClick()'>Go</button>
</div>
js:
function Controller($scope) {
$scope.items = [{
name: 'cb1',
selected:false,
formats: [{
title: 'rb1a',
selected: false
}, {
title: 'rb1b ',
selected: false
}]
}, {
name: 'cb2 ',
selected:false,
formats: [{
title: 'rb2a ',
selected: false
}, {
title: 'rb2b',
selected: false
}]
}];
$scope.onClick = function () {
console.log('clicked');
for (var i = 0; i < $scope.items.length; i++) {
if ($scope.items[i].selected) {
console.log('Controller item selected', $scope.items[i]);
}
for (var j = 0; j < $scope.items[i].formats.length; j++) {
if ($scope.items[i].formats[j].selected)
console.log('Controller format selected', $scope.items[i].formats[j]);
}
}
};
};
I have two questions:
when I select an rb in one group the selected rb in the other group gets unselected. So they are not groups yet. How can I make real groups? I thought by using the name directive, but how?
when I click the Go button I'd like to have set the selected properties in $scope.items correctly, both for checkboxes and radio buttons.
I tried a lot already, that's why I'm now just providing stripped down code.
http://jsfiddle.net/JeffW/crn1t7wy/
1) You need to set the name to be unique for each group of radio buttons. You can do it with the $index argument angular provides with ng-repeat.
2) You misplaced single quotes and this is why it doesn't work. HTML accepts only double quotes or single quotes as attribute values but not mixed.
<div ng-app ng-controller="Controller">
<div name="myForm">
<ul>
<li ng-repeat="item in items">
<label>
<input type="checkbox" ng-model='item.selected' />{{item.name}}</label>
<ul>
<li ng-repeat="format in item.formats">
<label>
<input type="radio" ng-value="format.title" ng-model="item.selectedRadio" name="myName{{$parent.$index}}"/>{{format.title}}</label>
</li>
</ul>
</li>
</ul>
</div>
<button ng-click='onClick()'>Go</button>
</div>
Fixed version is here:
http://jsfiddle.net/crn1t7wy/3/
EDIT: Fixed the answer based on the comments
I'm using https://drew.tenderapp.com/faqs/autosuggest-jquery-plugin/options-api to render an autocomplete field:
<h1>Quick Tags</h1>
<div class="fieldset">
{{ vocabularies.vocabulary_1.errors }}
<p>{{ vocabularies.vocabulary_1 }}</p>
<script type="text/javascript">
////http://code.drewwilson.com/entry/autosuggest-jquery-plugin
$("input#id_vocabulary_1").autoSuggest("/staff/taxonomy/ajax", {selectedItemProp: "name", selectedValuesProp: "name", searchObjProps: "name", startText: "Enter terms.", keyDelay: 50, minChars: 1, queryParam: "term", asHtmlID: "vocabulary_1", extraParams: "&app=taxonomy&model=TaxonomyData&vocabulary=1"});
</script>
</div>
Which renders a hidden field:
<li class="as-original" id="as-original-vocabulary_1">
<input id="vocabulary_1" type="text" name="vocabulary_1" maxlength="200" autocomplete="off" class="as-input">
<input type="hidden" class="as-values" name="as_values_vocabulary_1" id="as-values-vocabulary_1" value="test,new term,">
</li>
However, the values from this field are not present in the POST dictionary. What could be causing this problem?
I found out the problem; if the div that contains the form opening tag is closed, all fields after that are not contained in the POST dictionary.