I'm new to Ember JS and I am trying to figure out how to create Ember.Select which would allow to create new job for user.
This is part of the code from my form.handlebars:
{{view Ember.Select viewName="job-user"
contentBinding="users"
optionLabelPath="content.name"
optionValuePath="content.id"
}}
I am inside JobsNewController:
JP.JobsNewController = Ember.ObjectController.extend({
needs: ['users'],
users: [],
usersBinding: "controllers.users"
});
This is how my UsersController looks like:
JP.UsersController = Ember.ArrayController.extend();
It's working when I go to newJob screen from screen where I have UserController populated, but when I go directly to jobs/new it fails to populate select with data. How can I force Ember to load data for this select?
EDIT:
I had to setup routes correctly:
JP.JobsNewRoute = Ember.Route.extend( JP.JobsFormable, {
model: function() {
return JP.Job.createRecord();
},
setupController:function(controller,model) {
this._super(controller,model);
controller.set('usersForSelect', JP.User.find());
}
});
And I have also modified form:
{{view Ember.Select viewName="job-user"
contentBinding="usersForSelect"
optionLabelPath="content.name"
optionValuePath="content.id"
}}
Thanks #mavilein for hint
When you access the JobsNewRoute from the URL, the UsersController is not populated. So binding users in JobsNewController to UsersController content would be useless since UsersController is empty and was not populated.
You can solve this in two ways, both are in the JobsNewRoute.
First way, update your JobsNewRoute to fill the UsersController:
JP.JobsNewRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('users').set('content', JP.User.find());
}
});
Another way would be to remove the binding between JobsNewController.users and UsersController, and simply fill JobsNewController.users in the JobsNewRoute:
JP.JobsNewController = Ember.ObjectController.extend({
users: []
});
JP.JobsNewRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('users', JP.User.find());
}
});
Related
I have a laravel app and a Vue instance attached to the body (or a div, just inside the body).
const app = new Vue({
el: '#app'
});
I think it makes sense to use the Vue instance for stuff relating to the layout (eg header, nav, footer logic).
Now I have a form that is visible on a specific route (e.g. example.com/thing/create). I want to add some logic to it, e.g. hiding a field based on selected option in the form. It is logic meant for just this form (not to be reused). I prefer not to put all the logic inline with the form but put it in the app.js. I could put it in the Vue instance bound to the body but that sounds odd as it only applies to the form that is much deeper into the dom.
I want to leave the markup of the form in the blade template (that inherits the layout).
I tried creating a component but am not sure how to bind this inside the main Vue instance. What is the best way to handle things for this form, put it in the app.js and have it somewhat structured, putting the variables somewhat into scope. Or is it really necessary to remove the main Vue instance bound to the full layout code?
What I tried was something like this, but it does not work (attaching it to the <form id="object-form"> seems to fail:
var ObjectForm = {
template: function() { return '#object-form'},
data: function() {
return {
selectedOption: 1
}
},
computed: {
displayField: function() {
// return true or false depending on form state
return true;
}
}
};
Things do work if I remove the #app Vue instance or when I put everything directly in the app Vue instance. But that seems messy, if I have similar variables for another form they should be seperated somewhat.
I would appreciate some advice regarding the structure (differentiate page layout and page specific forms) and if possible some example to put the form logic inside the main app.js.
I hope this helps kind of break things down for you and helps you understand Vue templating.
It is best to take advantage of Vue's components. For you it would look something like this. Some of this code depends on your file structure, but you should be able to understand it.
In your app.js file (or your main js file)
Vue.component('myform',require('./components/MyForm.vue'));
const app = new Vue({
el: "#app"
});
Then create the MyForm.vue file
<template>
<form>
Put Your Form Markup Here
</form>
</template>
<script>
// Here is where you would handle the form scripts.
// Use methods, props, data to help set up your component
module.exports = {
data: function() {
return {
selectedOption: 1
}
},
computed: {
displayField: function() {
// return true or false depending on form state
return true;
}
},
methods: {
// add methods for dynamically changing form values
}
}
</script>
Then you will be able to just call in your blade file.
<myform></myform>
I found out how to do it. The trick was to use an inline template. Surround the form in the view with:
<object-form inline-template>
<form>...</form>
</object-form>
Where object-form is the name of the component. In the ObjectForm code above I remove the template, like this:
var ObjectForm = {
data: function() {
return {
selectedOption: 1
}
},
computed: {
displayField: function() {
// return true or false depending on form state
return true;
}
}
};
I attach the component within the root vue app like this:
const app = new Vue({
el: 'body',
components: {
'object-form': ObjectForm
}
});
This way I can use the form as it was generated from the controller and view and I can separate it from the root (attached to body) methods and properties.
To organize it even better I can probably store the ObjectForm in a seperate .vue file the way #Tayler Foster suggested.
I have created a route, for search which is searching users, and the application.hbs is having the search form, below are the segments of my code.
ROUTER router.js
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
export default Router.map(function() {
this.resource('search', {path: '/search/:citySef/:group'});
});
Route application.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return Ember.RSVP.hash({
groups: this.store.find('group'),
cities: this.store.find('city', {type: 'filled'})
});
},
setupController: function(controller, models) {
controller.setProperties(models);
}
});
Route search.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params){
return Ember.RSVP.hash({
users: this.store.find('user', {citySef: params.citySef, group: params.group})
});
},
setupController: function(controller, models) {
controller.setProperties(models);
}
});
Controller application.js
import Ember from 'ember';
export default Ember.Controller.extend({
isProcessing: false,
selectedCity: null,
selectedGroup: null,
actions: {
submitSearch: function () {
this.setProperties({
isProcessing: true
});
var citySef = this.get("selectedCity");
var group = this.get("selectedGroup");
console.log(citySef, group);
this.transitionToRoute('search', {citySef:citySef, group:group});
}
}
});
Controller search.js
import Ember from 'ember';
export default Ember.Controller.extend({
});
So basically I am creating a url like localhost:4200/search/City/Group to search users within a city with specific group.
When I press the search button, i can see that the URL is changed in the address bar from localhost:4200 to localhost:4200/search/City/Group but it dont send request to server to get data from REST, but when i do refresh the page, then it loads the data.
Edit
I have included pretty much everything apart from templates.
Edit 2015-04-22
I have uploaded the package (without bower and npm packages) to https://drive.google.com/file/d/0B5slzmNBINvNMHNjcXdqNWlCeUk/view?usp=sharing
I guess the problem in your case is model is not getting set properly first time.
Firstly you don't have to call setupController function explicitly when you have model function in your route, since model hook handles all these settings.
But if you want then the convention should be like -
setupController:function(controller,models){
controller.set('content',models);
}
try using setup controller function in above pattern.
As per Neha suggested, it was related to setupController
setupController: function (controller, models) {
if (models.citySef) {
controller.set('model', this.store.find('user', {citySef: models.citySef, group: models.group}));
} else {
controller.set('content', models);
}
}
So when page loads it goes to else condition, and if i transitiontoroute it goes in if conditon. I think this can be cleaned but so far it is working for me.
Is there any good solutions when editing a form to save the model only if the user clicked on the save button and retrieve the old datas if the user canceled the action ?
I've seen some solutions like duplicating the object that is data-binded with each form fields and set the the initial object with the duplicated one when it is saved.
If you could give answers without using ember data could be great.
I understand you would prefer a solution that doesn't use ember-data, but I would argue that using ember-data is best practices. Here is a solution using ember-data because I imagine a lot of people may come across this question...
If you set up your route as follows, it will do exactly that.
App.CommentEditRoute = Em.Route.extend({
model: function(params) {
return this.store.find('comment', params.comment_id);
},
actions: {
willTransition: function(transition) {
var model = this.get('controller.content');
if (model.get('isDirty')) {
model.rollback();
}
}
},
});
If you call this.get('content').save() in the controller (because the user clicked the save button) it will persist the changes through the adapter and isDirty will be set to false. Thus, the model will not rollback. Otherwise, if you did not call this.get('content').save() in the controller, the isDirty property will be true and the unsaved changes will be discarded. See the DS.Model docs for more info.
willTransition is an event automatically called when the route is about to change - you don't have to call it directly.
Your controller might look like this:
App.CommentEditController = Em.ObjectController.extend({
save: function() {
var _this = this;
_this.get('content').save().then(function() {
// Success
_this.transitionToRoute('comments');
}, function() {
// Failure
Em.assert('Uh oh!');
});
},
cancel: function() {
this.transitionToRoute('comments');
},
});
Also, be sure to utilize the default HTML form submission using a proper HTML button or input for submission so you can capture the submission event in your view as follows:
App.CommentEditView = Em.View.extend({
submit: function() {
this.get('controller').save();
},
});
I am on my way building a Fiori like app using SAPUI5. I have successfully built the Master page, and on item click, I pass the context and navigate to Detail page.
The context path from Master page is something like /SUPPLIER("NAME"). The function in App.controoler.js is as follows:
handleListItemPress: function(evt) {
var context = evt.getSource().getBindingContext();
this.myNavContainer.to("Detail", context);
// ...
},
But I would like to know how I can access this context in the Detail page. I need this because I need to use $expand to build the URL and bind the items to a table.
There is an example in the UI5 Documentation on how to deal with this problem using an EventDelegate for the onBeforeShow function which is called by the framework automatically. I adapted it to your use case:
this.myNavContainer.to("Detail", context); // trigger navigation and hand over a data object
// and where the detail page is implemented:
myDetailPage.addEventDelegate({
onBeforeShow: function(evt) {
var context = evt.data.context;
}
});
The evt.data object contains all data you put in to(<pageId>, <data>). You could log it to the console to see the structure of the evt object.
Please refer the "shopping cart" example in SAP UI5 Demo Kit.
https://sapui5.hana.ondemand.com/sdk/test-resources/sap/m/demokit/cart/index.html?responderOn=true
Generally, in 'Component.js', the routes shall be configured for the different views.
And in the views, the route has to be listened to. Please see below.
In Component.js:
routes: [
{ pattern: "cart",
name: "cart",
view: "Cart",
targetAggregation: "masterPages"
}
]
And in Cart.controller.js, the route has to be listened. In this example, cart is a detail
onInit : function () {
this._router = sap.ui.core.UIComponent.getRouterFor(this);
this._router.attachRoutePatternMatched(this._routePatternMatched, this);
},
_routePatternMatched : function(oEvent) {
if (oEvent.getParameter("name") === "cart") {
//set selection of list back
var oEntryList = this.getView().byId("entryList");
oEntryList.removeSelections();
}
}
Hope this helps.
Is there a way to have a form submit create an object in a store under ExtJs 4?
It seems strange to me that the grid is built completely around the store mechanism and I see no obvious way to plug a form into a store. But I am most likely just missing something.
You can add a model instance to a store upon form submit using this code:
onSaveClick: function()
{
var iForm = this.getFormPanel().getForm(),
iValues = iForm.getValues(),
iStore = this.getTasksStore();
iStore.add( iValues );
},
This is within an MVC controller, so this is the controller.
For model editing, you can 'bind' a form to a model instance using loadRecord:
iFormPanel.loadRecord( this.selection );
You can then update the model instance using updateRecord():
iFormPanel.getForm().updateRecord();
Just for fun (and as it might help some), it is similar to the following code:
onSaveClick: function()
{
var iForm = this.getFormPanel().getForm(),
iRecord = iForm.getRecord(),
iValues = iForm.getValues();
iRecord.set ( iValues );
},
If your store is has autoSync: true. An Update (or Create) call will be made via the configured proxy. If there's no autoSync, you'll have to sync your store manually.
You can subclass Ext.form.action.Action to provide load/save actions for a Form to be performed on a Store. The only gotcha is that somehow there's no "official" way to select any non-standard Action in Ext.form.Basic, so I'd suggest an unofficial override:
Ext.define('Ext.form.Advanced', {
override: 'Ext.form.Basic',
submit: function(options) {
var me = this,
action;
options = options || {};
action = options.submitAction || me.submitAction;
if ( action ) {
return me.doAction(action, options);
}
else {
return me.callParent(arguments);
}
},
load: function(options) {
var me = this,
action;
options = options || {};
action = options.loadAction || me.loadAction;
if ( action ) {
return me.doAction(action, options);
}
else {
return me.callParent(arguments);
}
}
});
And, having created the Actions you need, you could then use them in a Form Panel:
Ext.define('My.form.Panel', {
extend: 'Ext.form.Panel',
requires: [ 'Ext.form.Advanced' ],
loadAction: 'My.load.Action',
submitAction: 'My.submit.Action',
...
});
There are other ways and shortcuts though.