I'm just getting my feet wet with Ember.js, and I've hit something that I'm sure I'm not understanding.
I've got a selected object controller. It has content, which is an Ember.Object, which is the currently selected model. The model has a property (isDirty), and basically I'd like my save button on my form to be enabled only when the object is dirty and needs to be saved.
I've managed to bind up the form just fine, but the isEnabledBinding property on the save button is either not doing anything or I'm not hooking up the binding properly.
I've prepared a jsfiddle demonstrating my basic set up.
http://jsfiddle.net/blargity/fqc73/1/
How do I get the button to be enabled only when isDirty is true? The bindings should also work if the content property on the selected object controller changes.
I found a way to do this without using the now-deprecated Ember.Button.
In the handlebars template:
<button {{action "save" target="controller"}} {{bindAttr disabled="view.isNotDirty"}}>Save</button>
In the view:
isNotDirty: function(){
return !this.get('controller.content.isDirty')
}.property('controller.content.isDirty').cacheable()
(With the version of Ember I have, Ember.Binding.not does not exist. Maybe I need to update, but the docs don't show it either so perhaps it was actually removed.)
The problem is that there is no isEnabled property on Ember.Button. You need to bind to the disabled property.
One possibility is to create a custom Ember.Button which handles this for you, see http://jsfiddle.net/LabpW/.
Handlebars:
{{#view App.SaveModelButton modelBinding="model"}}Save{{/view}}
JavaScript:
App.SaveModelButton = Ember.Button.extend({
disabledBinding: Ember.Binding.not('model.isDirty')
});
The used Ember.Binding.not is just a shortcut for writing your own computed property, which would look like this:
App.SaveModelButton = Ember.Button.extend({
disabled: function() {
return !Ember.getPath(this, 'model.isDirty');
}.property('model.isDirty').cacheable()
});
I also refactored your code a bit:
You mixed create and extend: use create for instances and extend for classes. There is a good blog post about this
It's kind of a convention to use lowerCase for instances and UpperCase for classes, so it should be App.controller instead of App.Controller
Related
I have single page app made with ember.js and I have some problems with implementing social sharing.
I need to implement into hbs template something like this:
Link
However when this is rendered into browser there are additional script tags concatenated within the href string, eventually the link works and I am redirected but instead title I get something like this:
<script id='metamorph-190-start' type='text/x-placeholder'></script>
MyTitle
<script id='metamorph-19...
Where I need just MyTitle.
More specifically I use the following hbs template for facebook sharing, the model is initialized into the router:
<a href="https://www.facebook.com/login.php?next=http%3A%2F%2Fwww.facebook.com%2Fsharer%2Fsharer.php%3Fs%3D100%26p%255Btitle%255D%3D{{model.title}}%26p%255Burl%255D%3D{{model.url}}%26p%255Bsummary%255D%3D{{model.summary}}%26p%255Bimages%255D%255B0%255D%3D%2540Model.EventImage%2527%253EShare&display=popup"
target="_blank">
<img src="http://www.simplesharebuttons.com/images/somacro/facebook_img.png" alt="Facebook" />
</a>
I also tried third party libraries, like janrain, ShareThis or AddThis, but with them I had initialization problems, buttons were not shown at all when placed into template, and also had problems with messages customization from the model.
Thanks,
Igor
Approach 1 - Using unbound
To get rid of the metamorph tags surrounding your model value, try using the unbound option which does exactly that:
Link
Approach 2 - Computing the URL
In the case you need the model property to be bound and reevaluating when the model changes, then a different approach might be better like for example generating the URL in the controller backing up your template.
Assuming your controller is e.g. ApplicationController and the links live in the correspondent application template then you could do the following:
App.ApplicationController = Ember.ObjectController.extend({
url: function() {
var url = this.get('model.url');
var title = this.get('model.title');
// grab here as many properties you need from your model
var href = 'http://someaddres.com?u=%#&title=%#'.fmt(url, title);
return href;
}.property('model')
});
And then use the computed url property like this in your template:
<a {{bind-attr href=url}} target="_blank">Link</a>
Hope it helps.
This actually doesn't work that well since this will open a new browser tab\window instead of the desired popup window you get when using the suggested js code form facebook # https://developers.facebook.com/docs/plugins/share-button/
Unfortunately, you also need to create an 'action' within a Ember.View (for example) that calls window.open(url,"blah","width=300,height=500")
I'm new to AngularJS, and I'm dealing with a problem while implementing jQuery custom content scroller into my application.
I need the scroller to update, when I update the content with Angular, for this the scroller has an update method. My problem is, that I don't know where to call it. The markup for the content is the following:
<div class="scroll-list nice-scrollbars">
<ul class="gallery clearfix">
<li class="extra-alt" ng-repeat="item in relatedItems.data">
...
</li>
</ul>
</div>
I was trying to call the update method in success branch of Angular's $http.post:
$scope.relatedItems = $http.post($scope.url, {
'filterType': 'related', 'film': id
}).success(function() {
$(".nice-scrollbars").mCustomScrollbar('update');
});
This didn't work, I think it's because when the success method is called, the view content is not updated yet (I could see it using an alert function, the data appeared after closing the dialog box)
The only way I was able to make the scrollbars work was using the scroller's advanced property for watching the changes in the content (these are the options passed to the scrollbar):
var scrollbarOpts = {
scrollButtons:{
enable:true
},
advanced:{
updateOnContentResize: true
//#TODO: get rid of this, correctly find where to call update method
}
}
This is bad practice, as this options reduces the performance of the whole script.
I would like to know, where is the correct place to call jQuery methods needed to update DOM as needed, or how is such binding to view changes done correctly in AngularJS?
DOM manipulation should be done in a directive (not a controller). The directive should $watch() your model for changes, and the watch callback should perform the jQuery DOM manipulation. Sometimes $evalAsync is needed to run the jQuery code after Angular has updated/modified the DOM (but before the browser renders. Use $timeout if you want do perform some action after the browser renders). See this answer, where I provided a fiddle showing how to use a directive to $watch() a model property, and I used $evalAsync in a mock fetch() function.
For your particular case, I suggest you first try the following, in a directive:
scope.$watch("relatedItems.data", function() {
$(".nice-scrollbars").mCustomScrollbar('update');
});
and if that doesn't work, try this:
scope.$watch("relatedItems.data", function() {
scope.$evalAsync( // you might need to wrap the next line in a function, not sure
$(".nice-scrollbars").mCustomScrollbar('update')
);
});
I have a contentEditable view that when i focusOut i get the html entered and save it.
But when i remove all text from the contenteditable and then focus out i get the error
Uncaught Error: Cannot perform operations on a Metamorph that is not in the DOM.
Please see this jsfiddle and delete the value1 text and focus out.
http://jsfiddle.net/rmossuk/Q2kAe/8/
Can anyone help with this ?
best regards
Rick
Your EditableView sets up a display binding to its content.value:
<script type="text/x-handlebars" data-template-name="editable">
{{view.content.value}}
</script>
Ember performs its binding-updating magic by wrapping the relevant parts with marker script tags. Have a look at the resulting DOM:
Now you make your view editable. As soon as the user completely deletes the view's contents you will notice that the surrounding script tags are also removed (by the browser). At the moment Ember tries to update the display it won't find the necessary script tags and thus complains.
I don't think that you can make this approach to work reliably with contenteditable, since you won't be able to control the browser leaving the Ember surroundings intact. I guess you will need to take care of the view updating yourself: remove the binding, create a content.value observer and update the DOM explicitly:
App.EditableView = Em.View.extend({
contenteditable: "true",
attributeBindings: ["contenteditable"],
_observeContent: (function() {
this._displayContent();
}).observes('content.value'),
didInsertElement: function() {
this._displayContent();
},
_displayContent: function() {
this.$().html(this.get('content.value'));
},
...
Here is a JSFiddle with a demo of this solution: http://jsfiddle.net/green/BEtzb/2/.
(You could of course also use an Ember.TextField which uses a regular input field and provides all the binding magic, if that's all you need.)
I'm currently creating a footer with Save/Cancel/Delete, depending on where the user is. Now I'm trying to not show/render the Delete button when it's not required. How to achieve this using a variable from KnockoutJS (observable) as the operator in a ternary?
Current code doesn't work properly but below anyway.
<li>#(Global.ButtonCancel)</li>
<script>
var button = "<li>#(Global.ButtonDelete)</li>";
isEditingProduct ? button : false;
</script>
<li>#(Global.ButtonSave)</li>
The error i keep getting is that "isEditingProduct" is undefined. When i use it inline (outside the script), for a straight <li data-bind="isEditingProduct" ></li> with the other stuff inside it works. It hides the button, but leaves me with a gaping hole in the footer. Which is why I'm trying to get around it by not loading it in for rendering at all if it's not yet needed.
Any help would be appreciated.
Taking a look at your code, I'm confused.
No idea why you feel you need a ternary to hide/unhide elements.
Use the visible: binding.
<li data-bind="visible: isEditingProduct"></li>
isEditingProduct needs to be a property on your view model.
You could use visible or if binding:
<li>#(Global.ButtonCancel)</li>
<li>#(Global.ButtonDelete)</li>
<li>#(Global.ButtonSave)</li>
Read documentation about these bindings:
http://knockoutjs.com/documentation/if-binding.html
http://knockoutjs.com/documentation/visible-binding.html
Not sure where you "isEditingProduct" is defined but you can't simply reference a part of your View Model in JavaScript without fully qualifying it. Instead try:
myViewModel.isEditingProducts = true;
Also, the location of your script block is confusing. It shouldn't be in-lined between <li /> tags. The script will not necessarily execute at that time (as the browser is parsing your markup).
Working with Web2Py. I'm trying to attach some javascript either to a field (onchange) or to the form (onsubmit), but I see absolutely no way to pass such argument to crud.create or to form.custom.widget.
Anyone has an idea?
Of course there is a way. The appropriate way is to ask people on the web2py mailing list who know how to, as opposed to generic stack overflow users who will guess an incorrect answer. :-)
Anyway, assume you have:
db.define_table('image',
Field('name'),
Field('file', 'upload'))
You can do
def upload_image():
form=crud.create(db.image)
form.element(name='file')['_onchange']='... your js here ...'
form.element('form')['_onsubmit']='... your js here ...'
return dict(form=form)
Element takes the css3/jQuery syntax (but it is evaluated in python).
I do not believe there is a way to do this directly. One option is just to manipulate web2py generated HTML, it is just a string. Even cleaner, in my opinion, is just to bind the event using jQuery's $(document).ready() function.
Say you have a database table (all is stolen from web2py's docs):
db.define_table('image',
Field('name'),
Field('file', 'upload'))
With form:
def upload_image():
return dict(form=crud.create(db.image))
Embedded in a view (in the simplest manner):
{{=form}}
And you want to add an onblur handler to the name input field (added to the view):
<script type="text/javascript">
$(document).ready(function(){
$("#image_name").blur(function(){
// do something with image name loses focus...
});
});
</script>