I'm trying to bind a textfield to an value in object like this:
{{#each type in controller.currentTypes}}
<label>
{{type.name}}
{{view Ember.TextField valueBinding="controller.contextRemarks[type.id]"}}
</label>
{{/each}}
controller.currentTypes and controller.contextRemarks are only related in a loose one-to-many manner.
currentTypes = [{id: 1, name: 'Type name 1'}]
I don't know if I'm implementing an anti-pattern, but I don't think I can do without the dynamic index and Ember doesn't seem to allow me to use an index in a path.
I tried to solve this using an extra view or a helper, but keep getting stuck.
Is there an ember-ry solution for this?
So I worked around this problem by adding a computed property on the controller and storing the generated array in a private property:
App.MyController = Ember.Controller.extend({
remarks: function() {
var remarks = this.get('currentTypes') || [];
this._remarks = remarks.map(function(remark) {
return {
id: remark.id,
name: remark.name,
value: ''
};
});
return this._remarks;
}.property('currentTypes')
});
}
The view now looks like this:
{{#each remarks}}
<label>
{{name}}
{{view Ember.TextField valueBinding="value"}}
</label>
{{/each}}
Now I can access _remarks to find the user's input, this is the most ember like solution I can come up with and might be the "right solution. ;)
Related
On a rails 6 installation, I have the following:
Controller:
# app/controllers/foo_controller.rb
def bar
#items = [["firstname", "{{ FIRSTNAME }}"], ["lastname", "{{ LASTNAME }}"], ["company", "{{ COMPANY }}"]]
end
View:
# app/views/foo/bar.html.erb
<p>Quia <span data-field="firstname">{{ FIRSTNAME }}</span> quibusd <span data-field="firstname">{{ FIRSTNAME }}</span> am sint culpa velit necessi <span data-field="lastname">{{ LASTNAME }}</span> tatibus s impedit recusandae modi dolorem <span data-field="company">{{ COMPANY }}</span> aut illo ducimus unde quo u <span data-field="firstname">{{ FIRSTNAME }}</span> tempore voluptas.</p>
<% #items.each do |variable, placeholder| %>
<div data-controller="hello">
<input
type="text"
data-hello-target="name"
data-action="hello#greet"
data-field="<%= variable %>"
value="<%= placeholder %>">
</div>
<% end %>
and the relevant stimulus code (vanilla JS):
//app/javascript/controllers/hello_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "name" ]
greet() {
var elements = document.body.querySelectorAll('[data-field="' + this.nameTarget.dataset.field + '"]');
for (var i = 0; i < elements.length; i++) {
elements[i].innerText = this.nameTarget.value;
};
}
}
Now, as you might have guessed, the idea is to generate one <input> field per item from the #items hash, pre-filled with the relevant value and "linked" with a <span>, which it updates on value change. So far, everything works.
Here's my issue though. This part is plain old dirty vanilla js, which doesn't feel too 'stimulusy':
var elements = document.body.querySelectorAll('[data-field="' + this.nameTarget.dataset.field + '"]');
for (var i = 0; i < elements.length; i++) {
elements[i].innerText = this.nameTarget.value;
};
Surely there's some way to improve this. Any suggestion as to how to refactor this code in a more elegant way would be most welcome.
An approach would be to have two controllers, one for the 'thing that will change the content' (let's call this content) and another for the 'thing that will show any updated content somewhere else' (let's call this output).
Once you set up two controllers, it becomes a bit easier to reason about them as being discrete. One does something when a value updates from user interaction and the other should so something when it knows about an updated value.
Stimulus recommends cross controller coordination with events. JavaScript event passing is a powerful, browser native, way to communicate across elements in the DOM.
First, let's start with the simplest case in HTML only
In general, it is good to think about the HTML first, irrespective of how the content is generated on the server side as it will help you solve one problem at a time.
As an aside, I do not write Ruby and this question would be easier to parse if it only had the smallest viable HTML to reproduce the question.
Below we have two div elements, one sits above and is meant to show the name value inside the h1 tag and the email in the p tag.
The second div contains a two input tags and these are where the user will update the value.
I have hard-coded the 'initial' data as this would come from the server in the first HTML render.
<body>
<div
class="container"
data-controller="output"
data-action="content:updated#window->output#updateLabel"
>
<h1 class="title">
Hello
<span data-output-target="item" data-field="name">Joe</span>
</h1>
<p>
Email:
<span data-output-target="item" data-field="email">joe#joe.co</span>
</p>
</div>
<div data-controller="content">
<input
type="text"
data-action="content#update"
data-content-field-param="name"
value="Joe"
/>
<input
type="text"
data-action="content#update"
data-content-field-param="email"
value="joe#joe.co"
/>
</div>
</body>
Second - walk through the event flow
Once an input is updated, it will fire the conten#update event on change.
The data-content-field-param is an Action Parameter that will be available inside the event.params given to the class method update on the content controller.
This way, the one class method has knowledge of the element that has changed (via the event) and the field 'name' to give this when passing the information on.
The output controller has a separate action to 'listen' for an event called content:updated and it will listen for this event globally (at the window) and then call its own method updateLabel with the received event.
The output controller has targets with the name item and each one has the mapping of what 'field' it should referent in a simple data-field attribute.
Third - create the controllers
Below, the ContentController has a single update method that will receive any fired input element's change event.
The value can be gathered from the event's currentTarget and the field can be gathered via the event.params.field.
Then a new event is fired with the this.dispatch method, we give it a name of updated and Stimulus will automatically append the class name content giving the event name content:updated. As per docs - https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events
The OutputController has a target of name item and then a method updateLabel
updateLabel will receive the event and 'pull out' the detail given to it from the ContentController's dispatch.
Finally, updateLabel will go through each of the itemTargets and see if any have the matching field name on that element's dataset and then update the innerText when a match is found. This also means you could have multiple 'name' placeholders throughout this controller's scoped HTML.
class ContentController extends Controller {
update(event) {
const field = event.params.field;
const value = event.currentTarget.value;
this.dispatch('updated', { detail: { field, value } });
}
}
class OutputController extends Controller {
static targets = ['item'];
updateLabel(event) {
const { field, value } = event.detail;
this.itemTargets.forEach((element) => {
if (element.dataset.field === field) {
element.innerText = value;
}
});
}
}
An alternate approach is to follow the Publish-Subscribe pattern and simply have one controller that can both publish events and subscribe to them.
This leverages the recommended approach of Cross-controller coordination with events.
This approach adds a single controller that will be 'close' to the elements that need to publish/subscribe and is overall simpler to the first answer.
PubSubController - JS code example
In the controller below we have two methods, a publish which will dispatch an event, and a subscribe which will receive an event and update the contoller's element.
The value used by this controller is a key which will serve as the reference for what values matter to what subscription.
class PubSubController extends Controller {
static values = { key: String };
publish(event) {
const key = this.keyValue;
const value = event.target.value;
this.dispatch('send', { detail: { key, value } });
}
subscribe(event) {
const { key, value } = event.detail;
if (this.keyValue !== key) return;
this.element.innerText = value;
}
}
PubSubController - HTML usage example
The controller will be added to each input (to publish) and each DOM element you want to be updated (to subscribe).
Looking at the inputs you can see that they have the controller pub-sub and also an action (defaults to triggering when the input changes) to fire the publish method.
Each input also contains a reference to its key (e.g email or name).
Finally, the two spans that 'subscribe' to the content are triggered on the event pub-sub:send and pass the event to the subscribe method. These also have a key.
<body>
<div class="container">
<h1 class="title">
Hello
<span
data-controller="pub-sub"
data-action="pub-sub:send#window->pub-sub#subscribe"
data-pub-sub-key-value="name"
>Joe</span
>
</h1>
<p>
Email:
<span
data-controller="pub-sub"
data-action="pub-sub:send#window->pub-sub#subscribe"
data-pub-sub-key-value="email"
>joe#joe.co</span
>
</p>
</div>
<div>
<input
type="text"
data-controller="pub-sub"
data-action="pub-sub#publish"
data-pub-sub-key-value="name"
value="Joe"
/>
<input
type="text"
data-controller="pub-sub"
data-action="pub-sub#publish"
data-pub-sub-key-value="email"
value="joe#joe.co"
/>
</div>
</body>
I'm working on a basic reddit clone app with Rails and ember.js (via the ember-rails gem). Basically I have a 'post' model/controller in Rails which works correctly, but when I add a new post from the ember post model's create action, even if it fails the Rails validation, if I then go to the 'posts' index page which lists all the posts, I can see it there (i.e. ember is keeping the data). When I refresh it goes away, but I'm wondering what is the best way to purge that data so that it gets deleted upon rejection from the backend? Another odd thing is that simply going to the posts/new page at all creates a new blank post which is then visible on the Then on the client-side, I have the following files in app/assets/javascripts/routes:
posts_route.js:
RedditJp.PostsRoute = Ember.Route.extend({
model: function() {
return this.get('store').find('post');
}
});
posts_new_route.js:
RedditJp.PostsNewRoute = Ember.Route.extend({
model: function(){
return this.get('store').createRecord('post'); },
actions: {
create: function() {
var newPost = this.get('currentModel');
var self = this;
newPost.save().then(
function() { self.transitionTo('posts'); },
function() { }
);
}
}
});
Here's the form I'm trying to use to submit the data in posts/new.hbs:
<h1> Add a post </h1>
{{#each error in errors.title}}
<p>{{error.message}}</p>
{{/each}}
<form {{action "create" on="submit"}}>
<div>
<label>
Title<br/>
{{input type="text" value=title}}
</label>
</div>
<div>
<label>
Address<br/>
{{input type="text" value=address}}
</label>
</div>
<div>
<label>
Vote count<br/>
{{input type="text" value=voteCount}}
</label>
</div>
<button>Save</button>
</form>
and then in assets/javascripts/templates/posts/ I have index.hbs:
<h1>Posts</h1>
<ul>
{{#each}}
<li>{{title}} at {{address}} vote count: {{voteCount}}</li>
{{else}}
<li>There are no posts.</li>
{{/each}}
</ul>
and here's my router.js:
RedditJp.Router.map(function() {
this.resource('posts', function() {
this.route('new')
});
this.resource('home', function() {
});
});
RedditJp.IndexRoute = Ember.Route.extend({
redirect: function(){
this.transitionTo('home')
}
});
I was thinking I could just add a check in the posts/index.hbs file and only show records that aren't dirty, but there must be a cleaner way of doing it, so I'm wondering what would be considered best practice in this case (I'm thinking there should be some code I could add to the promise in posts_new_route.js to deal with this, but I can't quite figure it out).
Thanks a lot! And let me know if you need any additional info.
You can check if model isNew in template to hide new Record ( also you can use isEmpty property )
var record = store.createRecord('model');
record.get('isNew'); // true
record.save().then(function(model) {
model.get('isNew'); // false
});
In template will look like {{each model}}
{{#if model.get('isNew')}}
record.save().then(function(){
// Success callback
}, function() {
model..deleteRecord();
});
I've been following web tutorials to try to learn angularJS on a .NET MVC Application. All the tutorials seem to cover getting a list, getting an individual item etc.
What I want to do is allow the user to fill in an email address, I want to verify that email address against the database and return true or false if it existed. I'm then trying to put that value in the scope so I can do something in response to whether its true or false.
I'm using a single page app so this is the login html.
<form name="form" class="form-horizontal">
<div class="control-group" ng-class="{error: form.ValidEmailAddress.$invalid}">
<label class="control-label" for="ValidEmailAddress">Valid Email Address</label>
<div class="controls">
<input type="email" ng-model="item.ValidEmailAddress" id="ValidEmailAddress">
</div>
</div>
<div class="form-actions">
<button ng-click="login()" class="btn btn-primary">
Go!
</button>
<label ng-if="user.isAuthorised">Authorised</label>
<label ng-if="!user.isAuthorised">NotAuthorised</label>
</div>
</form>
In my app.js file I declare a loginCtrl controller when the url was /login so that's all fine. The logic that I'm calling on my button click is this:
var LoginCtrl = function ($scope, $location, $http, AuthorisedUser) {
$scope.login = function() {
var isValidUser = $http.get("/AuthorisedUser/IsValidUser/" + $scope.item.ValidEmailAddress);
$scope.user.isAuthorised = isValidUser;
} };
Which is then calling an MVC AuthorisedUserController class method:
public bool IsValidUser(string id)
{
var list = ((IObjectContextAdapter)db).ObjectContext.CreateObjectSet<ApprovedUser>();
var anyItems = list.Any(u => u.ValidEmailAddress == id);
return anyItems;
}
So it vaguely seemed to be working when I put in a value like "aaa" into the textbox. But as soon I try putting in an email address the value is undefined. Maybe I'm supposed to be doing a post but the only thing I can successfully hit my .NET controller with is by using get.
I'm sure I'm missing fundamental knowledge and potentially tackling this in the wrong way.
In case it helps I've created a module and defined factories like this:
var EventsCalendarApp = angular.module("EventsCalendarApp", ["ngRoute", "ngResource"]).
config(function ($routeProvider) {
$routeProvider.
when('/login', { controller: LoginCtrl, templateUrl: 'login.html', login: true }).
otherwise({ redirectTo: '/' });
});
EventsCalendarApp.factory('AuthorisedUser', function ($resource) {
return $resource('/api/AuthorisedUser/:id', { id: '#id' }, { isValidUser: { method: 'GET' } });
});
One of my questions is - should I be accessing the controller method using the $http object, or is there a way of using my factory declaration so that I can go something like:
AuthorisedUser.IsValidUser($scope.item.validEmailAddress)
I know in the tutorial I was following I could do stuff like:
CalendarEvent.save()
to be able to call a CalendarEventController post method.
What i think is, your get() function will return a promise. and you can't assign promise like this. so better try this approch once. I hope, it'd work. if not please let me know...
here I assume your first,second and third snippet of code works fine...
$http.get("/AuthorisedUser/IsValidUser/" + $scope.item.ValidEmailAddress).success(function (result, status) {
var isValidUser=result;
$scope.user.isAuthorised = isValidUser;
$scope.$apply();
}).error(function (result, status) {
//put some error msg
});
I have used multiple datepicker in angular directive and it gives me an error
Uncaught Missing instance data for this datepicker because if I removed one li then It doesn't set id in directive.
directive.js
app.directive('datepicker', function () {
return {
link: function($scope, element, attrs) {
setTimeout(function() {
$("#"+attrs.id).live('focus',function(){
console.log(attrs.id);
});
});
}
}
index.html
<tr ng-repeat="payment in invoice.payment" id="payment">
<td>
<input class="payment_date" id="{{$index}}" placeholder="Payment Date" type="text" ng-model="payment.date" datepicker/>
<br />
</td>
</tr>
How can I set ID in the directive.js?
I think you want to know when you can "get" (not "set") the ID in the directive. Here's how to do that:
app.directive('datepicker', function () {
return {
link: function($scope, element, attrs) {
element.bind('focus', function (){
console.log(attrs.id);
});
}
};
});
You should look at http://docs.angularjs.org/guide/directive#Attributes.
At the linking phase, it is not defined yet.
Also, I don't think you need to use ID selector, as you simply attach your event against element in your link function.
Have you looked at using AngularUI? Then you can use the JQuery passthrough without having to create your own directive (I also added ui-options as an example in case you decide to use it). Here is what it would look like:
<input class="payment_date" ui-jq="datepicker" ui-options="{dateFormat:'dd-mm-yy'}" placeholder="Payment Date" type="text" ng-model="payment.date" />
As for putting an ID on it, I am not sure why you would need one. If you want to get the value of a selected date, instead of doing this:
var myDate = $("#"+myIndex).val();
You should do this:
var myDate = invoice.payment[myIndex].date;
That is the beauty of angular, no need to use the DOM here. Let me know if you have any questions and I will be happy to answer them.
I've difficult to used new { attributes} in an HtmlHelper function. I'd like, for instance, to widen a TextBox. Should I use new { width = "50px"}, new { width = "50"}, or new { width = 50}, etc.
How many atttribues can I use?
What's the general rules?
Thanks helping
When adding HtmlAttributes in such a way it is important to keep in mind that the attributes you specify will get rendered as the html element's attributes. E.g. if you have:
<%= Html.TextBoxFor(... , new { width = "50px" }) %>
it will get rendered as
<input type="text" ... width="50px" />
In other words - exactly what you specify. Thus if you feel that it's difficult to decide what to write in the Helper, try to think what you want your Html to look like first, and go from there. You might want something like:
<%= Html.TextBoxFor(... , new { style = "width:50px;attribute2:value2;" }) %>
to get
<input type="text" ... style="width:50px;attribute2:value2;" />
But generally it is considered a good practice to separate layout from markup, so it's quite common to just apply a css-class to the element (note the # before the class name - it is required due to class being a keyword in C#):
<%= Html.TextBoxFor(... , new { #class = "mytextbox" }) %>
Where mytextbox is defined in a css file and specifies your width and other properties:
.mytextbox { width: 50px; attribute2: value2; ... }