I'm trying to extend Ember's TextField with UrlField so that if someone forgets to include http://, it does it for them.
Here's my View:
views/input-url.js
import Ember from 'ember';
export default Ember.TextField.extend({
type: 'url',
didInsertElement: function() {
this._super.apply(this, arguments);
this.formatValue();
},
onValueChange: function() {
this.formatValue();
}.observes('value'),
formatValue: function() {
var pattern = /^https{0,1}:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+/g;
if (pattern.test(this.get('value')))
return;
if (!pattern.test('http://' + this.get('value')))
return;
this.set('value', 'http://' + this.get('value'));
}
});
If I use it in my template like this, it works fine:
{{view "input-url" value=url}}
I prefer to use custom view helpers, so I created this (following the guide at the bottom of this page: http://guides.emberjs.com/v1.11.0/templates/writing-helpers/):
helpers/input-url.js
import Ember from 'ember';
import InputUrl from '../views/input-url';
export default Ember.Handlebars.makeBoundHelper(InputUrl);
Now trying to render this in my template doesn't work:
{{input-url value=url}}
I've also tried different permutations of this, including what's shown in the guide Ember.Handlebars.makeBoundHelper('input-url', InputUrl); (which throws an error), but I can't seem to get my input field to show up. What am I doing wrong?
Not sure what you are doing wrong with your view helper, but there is a much simpler solution: take advantage of the fact that Ember.Textfield is a component. http://emberjs.com/api/classes/Ember.TextField.html
Simply move views/input-url.js to components/input-url.js and get rid of your view helper.
Then {{input-url value=url}} should work automatically.
If you want do to this using a helper, you cannot extend Ember.TextField because extends Ember.Component and is not a Handlebars helper.
The way to do this using a helper would actually be simpler. Since you are using Ember-CLI, you can create a helper called "input-url" with the command ember g helper input-url and the only code you would need is the code within your formatValue() function:
helpers/input-url.js
// define patter globally so it's not recreated each time the function is called
var pattern = /^https{0,1}:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+/g;
export function inputUrl(value) {
if (pattern.test(value)) {
return value;
}
if (!pattern.test('http://' + value)) {
return value;
}
return 'http://' + value;
};
export default Ember.Handlebars.makeBoundHelper(inputUrl);
And you can use it like:
{{input-url PASS_YOUR_URL_HERE}}
Where the value you pass will be the value of the value variable within the helper.
You could also create a component, as #Gaurav suggested, using the exact code you have above, just in components/input-url.js instead and delete the helper cause it is not necessary anymore. You also have to edit the corresponding template of the component if you want it to display the value with a single handlebars expression:
templates/components/input-url.hbs
{{value}}
The usage with a component would be:
{{input-url value=PASS_YOUR_URL_HERE}}
Related
I am building an Ember tooltip module to create dynamic content on hover.
<div class="custom-tool-wrapper">
{{#custom-tool-tipster
side="right"
content=(or getContent question.id)
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
</div>
in the ember controller - the function doesn't return the variable "question.id" --- it comes back as 0 always - when it should be a string "q-1"
export default Ember.Component.extend({
getContent(tips){
console.log("tips1")
console.log("question", tips);
},
});
I think what you're actually trying to achieve is best done via computed property on the question model object (your question is still really vague).
content: computed('id', function(){
//this.tips is a part of the model object
//compute and return whatever the content is
return "content";
}
and then just say:
{{#custom-tool-tipster
side="right"
content=model.content
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
If you needed to actually invoke a function (which it's rare to think of an instance where the computed property isn't a better solution whenever state is involved), you would use a custom handlebars helper.
(or a b) is (a || b) and isn't function invocation like you're attempting if you're using the ember truth helpers lib for the or helper. It looks like you're trying to accomplish what ember-invoke allows
import Ember from 'ember';
import { helper } from '#ember/component/helper';
export function invokeFunction([context, method, ...rest]) {
if (typeof context[method] !== 'function') {
throw new Error(`Method '${method}' is not defined or cannot be invoked.`);
}
return Ember.get(context,method).apply(context, rest);
}
export default helper(invokeFunction);
which can be used like content=(invoke this "getContent" question.id) to invoke and return the value of a function on the passed in context object (the controller if this in the case of a route's template). Let me be clear, I think this invoke approach is a terrible idea and really gets rid of your separation of concerns and I'm not advocating that you do it. Templates shouldn't contain your logic and definitely shouldn't be calling arbitrary functions on the controller when you have such a nice facility like computed properties.
Views are gone since Ember 2.0.0, but you could do this:
// app/views/application.js or app/application/view.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: []
});
Since Ember CLI 2.7 this workaround no longer works, looks like the views folder is now being ignored. However, the Ember inspector still shows this for the application view:
view:foobar#view:toplevel
And the HTML is:
<div id="ember420" class="ember-view">
<h2>application</h2>
</div>
It still is a view, there must be a way to customize it.
You can use jQuery (via Ember.$) to manually add the class at some point in the application's startup. This will work:
// in app/routes/application.js
export default Ember.Route.extend({
// ...
activate() {
this._super(...arguments);
let root = Ember.getOwner(this).get('rootElement');
Ember.$(root).addClass('my-custom-class');
},
// ...
});
Since you are asking about the application route, there is no need to clean up after this in the deactivate hook.
This is a specialization of something I've done to facilitate tweaking of styles depending on the current route. Here's an instance initializer that will add a route--xyz class to the root element for the current route hierarchy:
import Ember from 'ember';
function toCssName(routeName) {
return `route--${routeName.dasherize().replace(/\./g, '-')}`;
}
export function initialize(appInstance) {
Ember.Route.reopen({
activate() {
this._super(...arguments);
Ember.$(appInstance.rootElement).addClass(toCssName(this.routeName));
},
deactivate() {
this._super(...arguments);
Ember.$(appInstance.rootElement).removeClass(toCssName(this.routeName));
}
});
}
export default {
name: 'css-route-name',
initialize
};
With this initializer, the root element will always have the class route--application in addition to any other active routes.
Problem can be solved via css:
body > .ember-view {
height: 100%;
}
As jquery selector 'body > .ember-view' should work too
Seems like the best option is to add a component to the application template. What makes it the best solution is that you don't need extra addons or hacks.
More context: https://github.com/emberjs/ember.js/issues/11486
// application template
{{#app-view-substitute}}
{{outlet}}
{{/app-view-substitute}}
Note: Not convenient for a large app where other developers have already made assumptions about the level of nesting of the elements. This adds one more level to every single element and CSS, even when carefully crafted, might break. An alternative is to: https://stackoverflow.com/a/40187809/7852
What I ended up doing is:
// top level component rendered in the application template
didInsertElement: function() {
this._super(...arguments);
this._addIdAndCSSClassToApplicationView();
},
_addIdAndCSSClassToApplicationView: function() {
let root = Ember.getOwner(this).get('rootElement'); // Ember >= 2.3
let applicationView = root.querySelector('.ember-view:first-child');
let idclass = 'myRequiredName';
applicationView.id = idclass;
let classes = applicationView.className;
applicationView.className = classes + ' ' + idclass;
}
Wrapping the outlet is cleaner, this is a hack, but it makes sense in a large app where other have already made assumptions about the levels of nesting in the app.
With Ember 2.2 I had to change the root line to something like this:
let root = Ember.$('.ember-application');
I have a template that includes a background image for it's items:
{{#each model as |item|}}
<div style="background-image: url('img/backgrounds/{{item.imageBackground}}');">
{{image.title}}
</div>
{{/each}}
This of course is no good, as binding to style-attribute is deprecated.
So I made a computed property on my controller that serves a htmlSafe string to bind, which is working as intended.
Since I need this - and images bound to a special link - in several templates I made 2 helpers that I want/tried to combine:
The first helper is working perfectly in several other templates (generates a params-string/link to a php-file that serves the desired image)
// helpers/imagelink.js
export default Ember.Helper.extend({
empty: "img/dummies/blank.png",
compute(params, hash) {
if(params[0]) {
let paramString = 'file='+params[0]+'&itemType='+hash.item+'&type='+hash.type;
return ENV.ajaxPrefix + ENV.apiNamespace + '/getimage?'+paramString;
} else {
// display dummy
return this.get('empty');
}
}
});
Now I wanted to make a second helper that somehow encapsulates the first helper and adds the needed 'style' string to the link:
// helpers/imagebackgoundstyle.js
import Ember from 'ember';
import { imagelink } from 'my-app-name/helpers/imagelink';
export default Ember.Helper.extend({
compute(params, hash) {
// ERROR HERE
let link = imagelink(params, hash);
return Ember.String.htmlSafe("background-image: url('"+link+"');");
}
});
calling that seceond helper like this:
<div style={{imagebackgroundstyle workgroup.imageBackground item='workgroup' type='imageBackground'}}>
The error I get here is imagelink.imagelink is not a function.
I've tried several variations, even odd stuff like imagelink.compute(params, hash), ...
Clearly I'm doing something wrong when importing the helper, but I just can't get around what....?
I've tried/viewed
Ember js use handlebars helper inside a controller?
and
Calling a handlebars block helper from another helper
and several more....
Didn't solve/are outdated.
I believe your is not a function errors are all related to your import syntax:
import { imagelink } from 'my-app-name/helpers/imagelink';
You are trying to import something that doesn't exist, as the imagelink helper is exported as default. So you'll have to use:
import imagelink from 'my-app-name/helpers/imagelink';
But you'll run into another problem with your code, so I would recommend changing it to this:
import Ember from 'ember'
import ImageLink from './image-link'
export default ImageLink.extend({
compute(params, hash) {
const val = this._super(params, hash)
return val + '2'
}
})
What you're doing here, is just extending the other helpers, calling it's compute function by using this._super(), and using the return value from that in your new helper.
Here is a twiddle with a working example.
I need to generate the url for given route from a helper.
How to generate url for a route in Ember.js pointed me to use the generate function. And yes it works as i need (Checked the functionality by making application route global). But i am not sure how to call it from inside a helper.
You were in a good direction, so you mainly solved this problem. :) There are two type of helper in Ember a simple function helper and the Class Based Helpers. We will use a Class Based Helper in this case.
As you have seen in your linked example, we need access to the main Router. We can do this with Ember.getOwner(this).lookup('router:main'). (Ember.getOwner() exists from v2.3, before v2.3 use this.container.lookup('router:main'))
For example, you have this map in your router.js:
Router.map(function() {
this.route('about');
this.route('posts', function() {
this.route('post', {path: '/:post_id'});
});
});
And if you create a helper for example with the name of url-for your template could contain these lines:
{{url-for 'about'}}
{{url-for 'posts'}}
{{url-for 'posts.post' 2}}
And your Class Based Helper could be the following:
// app/helpers/url-for.js
import Ember from 'ember';
export default Ember.Helper.extend({
router: Ember.computed(function() {
return Ember.getOwner(this).lookup('router:main');
}),
compute([routeName, ...routeParams]) {
let router = this.get('router');
return Ember.isEmpty(routeParams) ?
router.generate(routeName) : router.generate(routeName, routeParams[0]);
}
});
Demo on Ember Twiddle
I am using ember-cli and there is one controller which gets using a render helper and hence no route. Example
{{render 'ref-type' ref-type}}
Now inside the controller ref-type
export default Ember.Controller.extend({
actions{
isShown: function() {
var m = this.get('model'); //here model is undefined can i know why?
}
}
});
and model ref-type is
export default Ember.Object.extend({
getData: function(){
return 'xyz'; //data is returned hre
}
});
why am i not able to access model in the controller.
Adding a raw JSBin example
JSBIN
Should the model always be DS.Model.extend? i do not think so.
Also Instead of ref-type i have used 'sample' as the name, so that it is easier to understand
You never initialize your model. According to your JSBin example you must have property named sample in your TodosController. ember will not create an object by itself. I have edited your JSBin. It might not be the best approach but I tried to explain what is going on.
If you put log {{log sample}} just above your render helper you will notice that your sample property is already undefined.