Lets say I have a mustache button partial like this
<button name="{{name}}" class="btn">{{title}}</button>
Is it possible somehow to set the title when calling the partial directly like this
<form>
....
{{> button|name=foo,title=Cancel}} {{> button|name=bar,title=Submit}}
</form>
This would make it much easier to create views instead of something like this and creating a hash for each button.
<form>
....
{{#buttons}}
{{> button}}
{{/buttons}}
</form>
I am not sure you can do that but I worked around that using mustache functions
javascript
var partial = "<div>{{value}}</div>";
var data = {some_data:[1,2,3]};
var items = {func:function (){
return function (index){
return mustache.render(partial,{value : data.some_data[index]});
}
});
mustache.render(main,items);
mustache
{{#func}}1{{/func}}
{{#func}}2{{/func}}
It's not possible to do it in plain Mustache, but it's doable in Handlebars with helpers. Handlebars is an extension of Mustache, so you can switch to it just by replacing parser library. Here's how it might look in Handlebars:
JS Helper (there are also implementations for many other languages):
Handlebars.registerHelper('button', function(title, options) {
var attrs = Em.keys(options.hash).map(function(key) {
key + '="' + options.hash[key] + '"';
}).join(" ");
return new Handlebars.SafeString(
"<button " + attrs + ">" + title + "</button>");
});
Usage in template:
{{buttton title name="name" class="class"}}
More info is available here: http://handlebarsjs.com/
No, you cannot pass variable view data directly from the template using Mustache.
However, rendering your buttons as a section using a list is a suitable alternative as of current when using Mustache:
Partial: (buttons.mst)
{{#buttons}}
<button name="{{name}}" class="btn">{{title}}</button>
{{/buttons}}
Template:
<form>
...
{{> buttons}}
</form>
View:
buttons: [
{ name: 'foo', title: 'Cancel' },
{ name: 'bar', title: 'Submit' },
]
Output:
<form>
...
<button name="foo" class="btn">Cancel</button>
<button name="bar" class="btn">Submit</button>
</form>
Which actually works quite well since if no buttons were passed, the partial won't be rendered.
Hope it helps.
Related
I saw an example of linking directly to a form element using JsViews, which I found to be preferable to encapsulating the whole form in a template. Here is a jsfiddle example of what I'm trying to do, which partially works:
http://jsfiddle.net/30jpdnkt/
var app = {
selections: {
things: [
{ Name: "thingName1", Value: "thingValue1" },
{ Name: "thingName2", Value: "thingValue2" },
{ Name: "thingName3", Value: "thingValue3" }
]
},
formData: {
selectedThing: "thingValue1",
}
};
//how do I reference this template in-line, outside of another wrapping template?
$.templates({
theTmpl: "#theTmpl"
});
$("#content").link(true, app);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://www.jsviews.com/download/jsviews.js"></script>
<script id="theTmpl" type="text/x-jsrender">
<select id="thingChoice" data-link="formData.selectedThing">
<option value="-">Please select</option>
{^{for selections.things}}
<option data-link="value{:Value} {:Name} selected{:~selected === Value}"></option>
{{/for}}
</select>
</script>
<div id="content">
<!--this part works-->
<input data-link="formData.selectedThing trigger=true"/>
<!--this part does not display-->
<span {{for data tmpl="theTmpl"/}}></span>
</div>
The data-linked INPUT tag is correctly bound to the object, but I cannot find a working example of how to reference a compiled template in-line without encapsulating the entire form in another template. That it's possible to use data-link syntax outside of a template gives hope that it may be possible with correct syntax.
Is this possible?
Yes it is possible - it is what I call top-level data-linking. There will be new documentation topics on this coming very soon, but meantime you have this sample:
http://www.jsviews.com/#samples/editable/toplevel-for
And your jsfiddle - which I updated to make it work fully: http://jsfiddle.net/30jpdnkt/1/
<div id="content">
<input data-link="formData.selectedThing trigger=true"/>
<span data-link='{for tmpl="theTmpl"}'></span>
</div>
I'm trying to create a manually saved form with a moderate number of fields (let's say 20) in Ember.js (not using live bindings) and so far am confused about the correct way / best practice for doing so. I've found the following methods:
http://www.solitr.com/blog/2012/06/ember-input-field-with-save-button/
How to use one-way binding on emberjs?
https://stackoverflow.com/a/16473186/1248965
All of the above methods seem hacky to a degree; they either extend the text field or use a per-field observer, requiring you to list out each one. Is there some other way? Something like the 'unbound' helper, but allowing the auto-model updating magic / validation (via ember-data) on some action (like an 'unbound-until' or 'conditional-bind' or something)? I've gone through all the docs, SO, the github issues, the Ember forum, and the links above, and still feel like I must have missed something.
Basically, a way to say "do everything you would do with a normally bound form/fields, but only on a certain action, rather than in real time."
What you want is a "Buffered Proxy", where you temporarily store all changes to the model (you can catch those using setUnkownProperty) in a proxy object. Once you are happy with the changes, you'd copy all of the proxy data over into the actual object ("flush the data").
App.Heisenberg = {
firstName: 'Walter',
lastName: 'White',
};
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Heisenberg;
},
setupController: function(controller, model) {
controller.set('content', model);
}
});
App.IndexController = Ember.ObjectController.extend({
proxy: {},
setUnknownProperty: function(key, value) {
console.log("Set the unknown property: " + key + " to: " + value);
this.proxy[key] = value;
console.log(this.proxy);
},
flush: function() {
for(var key in this.proxy)
this.set('model.'+key, this.proxy[key]);
}
});
Template:
<script type="text/x-handlebars" data-template-name="index">
Saved Name: {{firstName}} {{lastName}}<br />
<br />
<form {{ action "saveEdit" on="submit" }}>
First Name: {{input type="text" valueBinding="firstName"}}<br />
Last Name: {{input type="text" valueBinding="lastName"}}<br />
<br />
<button {{ action "flush" }}>Flush</button>
</form>
</script>
This would make for a nice controller Mixin.
See this jsBin for a live example.
I found a workaround, but I'm not 100% happy with it:
In my "editing" template, I have:
<form {{ action "saveEdit" on="submit" }}>
Title: {{input type="text" value=title}}
<input type="submit" value="Save">
<button {{ action "cancelEdit" }}>Cancel</button>
</form>
Then in my associated controller, I do:
cancelEdit: function() {
var entry = this.get('model');
this.set('isEditing', false);
entry.rollback();
},
saveEdit: function() {
var entry = this.get('model');
this.set('isEditing', false);
entry.save().then(
function() {
console.log('Saved!');
}
I simply hide the fields where the "live updating" would show. I still would like to find a way to temporarily turn off the binding until I trigger my "saveEdit" action, since this still seems inelegant.
See http://jsfiddle.net/4ZyBM/6/
I want to use Bootstrap for my UI elements and I am now trying to convert certain elements to Ember views. I have the following problem:
I embed an input element in a DIV with a given class (control-group). If a validation error occurs on the field, then I want to add an extra class "error" to the DIV.
I can create a view based on the Ember.TextField and specify that if the error occurs the ClassNameBinding should be "error", but the problem is that class is the set to the input element and not to the DIV.
You can test this by entering a non alpha numeric character in the field. I would like to see the DIV border in red and not the input field border.
HTML:
<script type="text/x-handlebars">
<div class="control-group">
{{view App.AlphaNumField valueBinding="value" type="text" classNames="inputField"}}
</div>
</script>
JS:
App.AlphaNumField = Ember.TextField.extend({
isValid: function () {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value'),
classNameBindings: 'isValid::error'
})
Can I set the classNameBindings on the parent element or the element closest to the input ? In jQUery I would use:
$(element).closest('.control-group').addClass('error');
The thing here is that without using jQuery you cannot access easily the wrapping div around you Ember.TextField's. Also worth mentioning is that there might be also a hundred ways of doing this, but the simplest solution I can think of would be to create a simple Ember.View as a wrapper and check the underlying child views for validity.
Template
{{#view App.ControlGroupView}}
{{view App.AlphaNumField
valueBinding="value"
type="text"
classNames="inputField"
placeholder="Alpha num value"}}
{{/view}}
Javascript
App.ControlGroupView = Ember.View.extend({
classNameBindings: 'isValid:control-group:control-group-error',
isValid: function () {
var validFields = this.get('childViews').filterProperty('isValid', true);
var valid = validFields.get('length');
var total = this.get('childViews').get('length')
return (valid === total);
}.property('childViews.#each.isValid')
});
App.AlphaNumField = Ember.TextField.extend({
isValid: function () {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value')
});
CSS
.control-group-error {
border:1px solid red;
padding:5px;
}
.control-group {
border:1px solid green;
padding:5px;
}
Working demo.
Regarding bootstrap-ember integration and for the sake of DRY your could also checkout this ember-addon: https://github.com/emberjs-addons/ember-bootstrap
Hope it helps.
I think that this is the more flexible way to do this:
Javascript
Boostrap = Ember.Namespace.create();
To simplify the things each FormControl have the properties: label, message and an intern control. So you can extend it and specify what control you want. Like combobox, radio button etc.
Boostrap.FormControl = Ember.View.extend({
classNames: ['form-group'],
classNameBindings: ['hasError'],
template: Ember.Handlebars.compile('\
<label class="col-lg-2 control-label">{{view.label}}</label>\
<div class="col-lg-10">\
{{view view.control}}\
<span class="help-block">{{view.message}}</span>\
</div>'),
control: Ember.required()
});
The Boostrap.TextField is one of the implementations, and your component is a Ember.TextField. Because that Boostrap.TextField is an instance of Ember.View and not an Ember.TextField directly. We delegate the value using Ember.computed.alias, so you can use valueBinding in the templates.
Boostrap.TextField = Boostrap.FormControl.extend({
control: Ember.TextField.extend({
classNames: ['form-control'],
value: Ember.computed.alias('parentView.value')
})
});
Nothing special here, just create the defaults values tagName=form and classNames=form-horizontal, for not remember every time.
Boostrap.Form = Ember.View.extend({
tagName: 'form',
classNames: ['form-horizontal']
});
Create a subclass of Boostrap.Form and delegate the validation to controller, since it have to be the knowledge about validation.
App.LoginFormView = Boostrap.Form.extend({
submit: function() {
debugger;
if (this.get('controller').validate()) {
alert('ok');
}
return false;
}
});
Here is where the validation logic and handling is performed. All using bindings without the need of touch the dom.
App.IndexController = Ember.ObjectController.extend({
value: null,
message: null,
hasError: Ember.computed.bool('message'),
validate: function() {
this.set('message', '');
var valid = true;
if (!/^[a-z0-9]+$/i.test(this.get('value'))) {
this.set('message', 'Just numbers or alphabetic letters are allowed');
valid = false;
}
return valid;
}
});
Templates
<script type="text/x-handlebars" data-template-name="index">
{{#view App.LoginFormView}}
{{view Boostrap.TextField valueBinding="value"
label="Alpha numeric"
messageBinding="message"
hasErrorBinding="hasError"}}
<button type="submit" class="btn btn-default">Submit</button>
{{/view}}
</script>
Here a live demo
Update
Like #intuitivepixel have said, ember-boostrap have this implemented. So consider my sample if you don't want to have a dependency in ember-boostrap.
I am trying to display the details of items in a list. This should be done by lazy loading the template (DOM for the details), because the template is very large and i've got many items in the list so a ng-show with ng-include is not working, since it is compiled into the DOM and makes the performance very bad.
After experimenting I figured out a solution, only working with a inline template. I am using a click handler to render the HTML with the detail-view directive to the DOM.
HTML
<div ng-controller="Ctrl">
{{item.name}} <button show-on-click item="item">Show Details</button>
<div class="detailView"></div>
<div ng-include="'include.html'"></div>
</div>
<!-- detailView Template -->
<script type="text/ng-template" id="detailView.html">
<p>With external template: <span>{{details.description}}</span></p>
</script>
Show On Click Directive
myApp.directive("showOnClick", ['$compile', '$parse', function($compile, $parse) {
return {
restrict: 'A',
scope: {
item: "=item"
},
link: function (scope, element, attrs) {
// Bind the click handler
element.bind('click', function() {
// Parse the item
var item = $parse(attrs.item)(scope);
// Find the element to include the details
var next = $(element).next('div.detailView');
// Include and Compile new html with directiv
next.replaceWith($compile('<detail-view details="item"></detail-view>')(scope));
});
}
};
}]);
Detail View Directive:
myApp.directive("detailView", ['$parse', '$templateCache', '$http', function($parse, $templateCache, $http) {
return {
restrict: 'E',
replace: true,
templateUrl: 'detailView.html', // this is not working
// template: "<div>With template in directive: <span>{{details.description}}</span></div>", // uncomment this line to make it work
link: function (scope, element, attrs) {
var item = $parse(attrs.details)(scope);
scope.$apply(function() {
scope.details = item.details;
});
}
};
}]);
Here is the full example on
Plunker
Is there a way to improve my solution, or what am I missing to load the external template?
Thanks beforehand!
You can also look at ng-if directive in Angular version 1.1.5 . ng-if would only render the html if condition is true. So this becomes
<div ng-controller="Ctrl">
{{item.name}} <button ng-if="showDetails" item="item" ng-click='showDetails=true'>Show Details</button>
<div class="detailView"></div>
<div ng-include="'include.html'"></div>
</div>
By just using ng-include:
<div ng-controller="Ctrl" ng-init="detailsViewTemplateSource='';">
{{item.name}}
<button ng-click="detailsViewTemplateSource = 'detailView.html'">
Show Details
</button>
<div ng-include="detailsViewTemplateSource"></div>
</div>
<!-- detailView Template -->
<script type="text/ng-template" id="detailView.html">
<p>With external template: <span>{{details.description}}</span></p>
</script>
I have started to look at ember js but I am having problems getting my head around the view - template part, if I want one text box to fire a create event this is simple enough I use the insertNewline function on the Ember.TextField view, however most web application require a form to be filled out and submitted when a button is pressed, I can't seen to get the a view working that has multiple text input boxes on it.
I have followed the example on git hub https://github.com/mikecrittenden/tangletube however he seems to be referencing DOM elements directly from the view rather than Binding to properties of the view.
Does anyone have an example of an ember project that uses multi text field forms.
On a side note: There seems to be no standard structure to developing ember applications, every example I have looked at does things completely differently.
Here's a very simple example showing two ways of using multiple text fields in a view: http://jsfiddle.net/tomwhatmore/HEaGm/
The first one binds the textfields to their view using viewName, which lets the view access each of them using this.get('whateverYouPutAsViewName').
The second binds the values of the textfields directly to an Ember object, by using valueBinding. Any changes you make to the fields will automatically update the object.
Both have a button which triggers a simple action which use the values to show how they are accessed in the view. Hopefully the code is pretty self-explanatory.
HTML:
<script type="text/x-handlebars">
{{#view App.HelloView}}
{{view Ember.TextField viewName="firstName" placeholder="first name"}}
{{view Ember.TextField viewName="lastName" placeholder="last name"}}
<button {{action "hello"}}>Say Hello</button>
{{/view}}
{{#view App.BoundView}}
{{#with person}}
{{view Ember.TextField valueBinding="firstName"}}
{{view Ember.TextField valueBinding="lastName"}}
{{/with}}
<button {{action "hello"}}>Say Hello</button>
{{/view}}
</script>
JS:
App = Ember.Application.create()
App.HelloView = Ember.View.extend({
hello: function() {
alert("Hello " + this.get('firstName').get('value') + " " + this.get('lastName').get('value'));
}
});
App.Person = Ember.Object.create({
'firstName': 'John',
'lastName': 'Doe',
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
App.BoundView = Ember.View.extend({
personBinding: 'App.Person',
hello: function() {
alert("hello " + this.get('person').get('fullName'));
}
})
The simple bindings example app on the emberjs site (http://emberjs.com/examples/) has multiple text boxes.
You may also want to checkout the sproutcore 2 (ember's previous name) demo app contest. http://blog.sproutcore.com/announcing-the-winners-of-the-demo-apps-contest/.
Generally speaking, to convert sproutcore 2 to emberjs, just change the SC namespace to Ember.