I am trying to implement Typeahead with an elasticsearch backend. The search itself seems to be working, now I am trying to tweak the optic. I would like to use a Ember.Handlebars helper that I wrote. My first attempt was using handelbars as template engine:
App.SearchComponent = Ember.TextField.extend({
didInsertElement: function() {
App.UserSearchEngine.initialize();
this.initializeTypeahead();
},
initializeTypeahead: function(){
var _this = this;
this.typeahead = this.$().typeahead({
hint: true,
highlight: true,
minLength: 3
},
{
name: _this.$().attr('id') || "typeahead",
// template: 'My: {{firstName}}',
limit: this.get("limit") || 5,
source: App.UserSearchEngine.ttAdapter(),
templates:{
suggestion: Ember.Handlebars.compile('{{firstName}} {{lastName}}')
}
}
);
}
});
This is giving me an error:
Uncaught TypeError: Cannot read property 'container' of undefined
caused by Ember in the "_triageMustache" helper in the line
var helper = Ember.Handlebars.resolveHelper(options.data.view.container, property);
This is probably due to the fact that I try to compile a template directly.
If I would use Handlebars.compile() instead of Ember.Handlebars.compile() it would work. It seems that the context is not correct.
You actually should use regular Handlebars.compile here. The Typeahead library doesn't know anything about Ember bound templates, so it doesn't know how to invoke them.
Alternatively, your template is so simple it could just be a literal function, to save yourself the hassle of shipping a Handlebars compiler in production or precompiling this one specially:
templates: {
suggestion: function(context){ return context.firstName + ' ' + context.lastName; }
}
Related
I have two components:
IfNodeComponent:
#Component({
template: '<se-dynamic-condition (questionLinkClicked)="onQuestionClicked" [condition]="node.condition"></se-dynamic-condition>',
selector: 'se-node-if'
})
export class NodeIfComponent {
#Input() node: NodeProperties;
onQuestionClicked(event: IQuestionLinkClickEvent): void {
// do stuff
}
}
and DynamicConditionComponent:
#Component({
template: '<p>My original template</p>',
selector: 'se-dynamic-condition'
})
export class DynamicConditionComponent {
#Input() condition: Condition;
#Output() questionLinkClicked = new EventEmitter<IQuestionLinkClickEvent>();
}
I am writing a test to check that the [condition] binding is attached to the se-dynamic-condition component inside the if node template. To do this I am overriding the template of the DynamicConditionComponent to simply be {{condition | json}}. This then allows me to compare the JSON and assert that it is identical to the condition that should be passed in.
Before RC5 I used the OverridingTestComponentBuilder to achieve this. But since I've just upgraded to RC5, I am rewriting that test to use the TestBed instead. This is not going too well. Here is how it looks:
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [NodeIfComponent, DynamicConditionComponent]
});
TestBed.overrideComponent(DynamicConditionComponent, {
set: {
template: '{{condition | json}}'
}
});
fixture = TestBed.createComponent(NodeIfComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
component.node = {some:'data'};
fixture.detectChanges();
});
it('should display a dynamic condition component and pass the condition to it', () => {
let dc = element.querySelectorAll('se-dynamic-condition');
expect(dc.length).toEqual(1, 'Dynamic condition component is found');
expect(dc[0].innerHTML).toEqual(JSON.stringify({some:'data'}, null, 2));
});
However, running this test fails with the following error:
Can't bind to 'condition' since it isn't a known property of 'se-dynamic-condition'.
If I don't override the template for DynamicConditionComponent, then I don't get the error, but understandably my test fails. And if I remove the property binding from the IfNode template, then I don't get the error, but again, the test fails as expected. The error message points towards the se-dynamic-condition component not being registered in the same module. But it is, and the code works when I run it. It is just the test that is the problem, which doesn't use the module definition anyway. That's what the TestBed.configureTestingModule statement is for.
So it appears that overriding a template also loses the condition property associated with the component.
Am I doing something fundamentally wrong here? Examples I have seen elsewhere of overriding the template work fine, but I haven't seen any that override a template with an input property (and then try to assign a value to that property).
Any help would be greatly appreciated.
This is a bug in RC5 fixed in RC6 by https://github.com/angular/angular/pull/10767
For now as a workaround re-specify the inputs in the override statement.
use
TestBed.overrideComponent(DynamicConditionComponent, {
set: {
template: '{{condition|json}}',
inputs: ['condition'],
}
});
I'm trying to display a simple template, but it seems like the template doesn't get added, as there is nothing added to the DOM. The code is called for sure and a container has the method setTpl(tpl). What am I doing wrong? The sample above is the most simple example I could imagine, but it doesn't work!
Ext.define('MyApp.view.sample', {
extend: 'Ext.Container'
config: {},
initialize: function() {
this.callParent();
var sampleText = '<div> why?? </div>';
var t = new Ext.Template(
sampleText,
{
compiled: true
}
);
t.compile();
this.setTpl(t);
},
});
HTML = template + data. So your next step is to call setData. Check the docs for tpl. If what you want is to plug in some raw HTML that doesn't depend on data, you've got the html config (and the corollary method setHTML). Last advice, if that's just for rendering some HTML, you don't need to use a container, a Component would be enough.
You have created a class, but you also need to instantiate it. Try something like this:
Ext.create('MyApp.view.sample', {
renderTo: 'some-div-id',
// any other necessary config options
// (see http://docs.sencha.com/extjs/3.4.0/#!/api/Ext.Container)
});
I am trying to inject another component into an element that is rendered by the template of another Coomponent..but in the afterrender event, the template is yet to be rendered so the call to Ext.get(el-id) returns null: TypeError el is null.
tpl:
new Ext.XTemplate(
'<tpl for=".">',
'<ul>',
'<li class="lang" id="cultureSelector-li"></li>',
'</ul>',
'</tpl>'
),
listeners: {
afterrender: {
fn: function (cmp) {
console.log(Ext.get('cultureSelector-li')); // < null :[
Ext.create('CultureSelector', {
renderTo: 'cultureSelector-li'
});
}
}
},
So when can I add this component so that the element is targeting has been created in the DOM?
I think it depends on the component that you are working with. For example, the Data Grid View has a "viewready" event that would suite your needs, and depending what you are attempting, the "boxready" function could work for combo box (only the first render though). Other than that, you can either go up through the element's parent classes searching for the XTemplate render function being called (might be in the layout manager) and extend it to fire an event there, or risk a race condition and just do it in a setTimeout() call with a reasonable delay.
I ended up having to do the work myself. So, I now have the template as a property called theTpl, and then rendered it in beforerender, and then i was able to get a handle on the element in afterrender. This seems wholly counter-intuitive, does anyone have any insight?
beforeRender: {
fn: function (me) {
me.update(me.theTpl.apply({}));
}
},
edit in fact I just extended Component thus:
Ext.define('Ext.ux.TemplatedComponent', {
extend: 'Ext.Component',
alias: 'widget.templatedComponent',
template: undefined,
beforeRender: function () {
var me = this;
var template = new Ext.XTemplate(me.template || '');
me.update(template.apply(me.data || {}));
me.callParent();
}
})
...template accepts an array of html fragments
Turns out I was using the wrong things - apparently we should be using the render* configs for this type of thing (so what are thetpl & data configs for?)
Here's a working fiddle provided for me from the sencha forums:
http://jsfiddle.net/qUudA/10/
How are people handling client side validation and ember?
Is there anything out of the box or a plugin that handles validation or are people just rolling their own?
https://github.com/dockyard/ember-validations might be useful. It also hooks up to Ember-easy-form
I would extend Ember.TextField (or whatever input type your validating) and use classBinding with a computed property. Here is the sample: http://jsfiddle.net/caligoanimus/7UNRd/
template:
<script type="text/x-handlebars" >
{{view App.AlphaNumField
placeholder="alpha-numeric data only"
valueBinding="App.alphaNumInput"}}
</script>
application:
App = Ember.Application.create({
AlphaNumField: Ember.TextField.extend({
isValid: function() {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value'),
classNameBindings: 'isValid:valid:invalid'
})
});
Another fully supported option and very logical if you are using bootstrap is to use bootstrap-validation plugin. In ember (ember-cli) this should be installed using bower:
bower install --save bootstrap-validation
then in ember-cli-build you must import dependencies:
//bootstrap-validator
app.import('bower_components/bootstrap-validator/dist/validator.js');
app.import('bower_components/bootstrap-validator/dist/validator.min.js');
This solution allows you to validate at html level, letting bootstrap do the 'dirty' job. For standard validations this will do the job simple and effortless.
I have been handling it in a very similar way to #caligoanimus, but validating on the focus out of the text box and appending an error message.
code:
App.TextFieldEmpty = Ember.TextField.extend({
focusOut: function() {
var valid = this.get('value') ? valid = true : valid = false;
this.$().next(".err").remove();
if(!valid){
this.$().addClass("invalid").after("<span class='err'>This field is required</span>");
} else {
this.$().removeClass("invalid")
}
}
});
Template:
<script type="text/x-handlebars">
{{view App.TextFieldEmpty}}
</script>
JSBIN:
http://jsbin.com/uriroYO/6/edit?html,js,output
I've had a lot of success with jQuery Validate:
App.MyView = Ember.View.extend({
templateName: 'my-form-template',
didInsertElement: function(){
$("#myForm").validate({
rules:{
fname:{
required: true,
maxlength: 50,
},
lname:{
required: true,
maxlength: 50,
},
email: {
required: true,
email: true,
maxlength: 200,
},
},
messages: {
email: {
email: "Enter a valid email address.",
},
},
});
}
});
Just using the Ember input helper, it's made my form validation very clean. You can take your jQuery Validate script and place it in a .js file as a function and just call that on didInsertElement.
jQuery Validate adds error messages below your fields with the message relating to the rule, and also allows you to trigger validation from any of your actions or events through the .valid() method.
We have created our own text fields which raise validation errors on focus out, and other events and other text fields extend them:
App.PhoneNumberField = App.TextField.extend({
validate: function(value) {
var self = this.$('input');
var id = self.attr("id");
var e164PhoneNumber = formatE164("AU",value);
var valid = true;
if(self.val().trim().length == 0 ){
valid = true;
}else{
valid = isValidNumber(e164PhoneNumber);
}
if (!valid) {
self.trigger(Storm.invalidInputEvent(id));
this.setErrorMessage("error","Phone Number does not look right");
}else {
self.trigger(Storm.validInputEvent(id));
this.clearMessages();
}
},
keyUp: function(evt) {
if(evt.keyCode >= 37 && evt.keyCode <=40)
{
return;
}
var textValue = this.$("input").val();
var input = this.$().find('input');
var formattedNumber = this.formatInput(textValue);
input.val(formattedNumber);
this.set('data',formattedNumber);
},
value: function() {
var phoneNumber = this.get('data');
if (phoneNumber) {
return phoneNumber;
} else {
return "";
}
}.property('data'),
data: null,
placeholder: function() {
return "";
}.property('placeholder'),
formatInput: function(textValue){
var formattedPhoneNumber = formatLocal("AU",textValue);
return formattedPhoneNumber;
}
});
I would use a model / persistance layer which uses a conventional "errors" object to save validation errors on the model.
Since Ember shines when it comes to observing changes, I would observe the changing errors object to determine whether or not should there be shown a validation message.
In my current setup I'm using Tower.js as framework, because it uses Ember as the View layer, and has a strong model / persistance layer. This layer allows me to define validations on model level. Each time I try to persist a model, it is validated and an error is thrown. In my views, this error aborts the "ideal" path and does not keep executing the workflow, instead it renders the validation errors in the template.
So far I have been using EJS templates in my Rails/Backbone.js application.
I really want to start using backbone.marionette. What can I do to make it work with EJS?
MyView = Backbone.Marionette.ItemView.extend({
template: "#some-template"
});
It says in the docs that I need to provide a jQuery selector to the template attribute but I don't think that my EJS templates have one.
Update:
This is how I would use templates to render my views normally:
TasksTree.Views.TaskItem = Backbone.View.extend({
...
render: function() {
...
this.$el.html(JST['tasks_tree/item'](options));
return this;
}
})
And I have templates folder with item.jst.ejs file that looks like that:
<li>
<label><%= taskTitle %></label>
</li>
My templates folder is included in application.js
There's a section in the docs that shows several examples of replacing the rendering mechanism, as well:
http://derickbailey.github.com/backbone.marionette/#backbone-marionette-renderer/custom-template-selection-and-rendering
Seeing that JST provides the templates for you, and you don't need to cache them in any other way, though, you could skip past most of the functionality built in to Marionette's Renderer object and replace the render function entirely.
Backbone.Marionette.Renderer.render = function(template, data){
return JST[template](data);
}
You would also replace the use of teh template attribute on views with the template path instead of a jquery selector:
Backbone.Marionette.ItemView.extend({
template: "tasks_tree/item"
});
Hope that helps. If not, let me know.
I found this reference to be quite helpful: https://github.com/marionettejs/backbone.marionette/wiki/Using-jst-templates-with-marionette
You could replace the render function with something along these lines, which provides better error handling and flexibility.
Marionette.Renderer.render = function(template, data) {
if(typeof template === 'function') {
return template(data);
}
else {
if(!JST[template]) throw "Template '" + template + "' not found!";
return JST[template](data);
}
};
You can then specify the template path (as previously mentioned):
Backbone.Marionette.ItemView.extend({
template: "tasks_tree/item"
});
Or if your template is very simple, you can use a function to return just a string:
Backbone.Marionette.ItemView.extend({
template: function(data) {
return "<div>" + data.attribute + "</div>";
}
});