I have a ViewModel that contains some initial data... this initial data is based off of a global variable that I have created. In the ViewModel, I have a formula that does some logic based on the data set from the global variable. The interesting thing is, this formula does not fire when the ViewModel is created. I'm assuming this is because the Something.Test property does not exist, so the ViewModel internals have some smarts to not fire the method if that property does not exist.
If the property doesn't exist, how do I fire the formula anyway? I know I could look for Something check to see if it has the property Test, but I'm curious why this example wouldn't work. Here's the example:
Ext.application({
name : 'Fiddle',
launch : function() {
// Define global var Something
Ext.define('Something', {
singleton: true
});
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myView',
data: {
Something: window.Something
},
formulas: {
testSomething: function(getter) {
console.log('here', getter('Something.Test'));
return getter('Something.Test');
},
myTitle: function(getter) {
return 'My Title';
}
}
});
Ext.define('MyView', {
extend: 'Ext.panel.Panel',
bind: {
title: '{myTitle}'
},
viewModel: {
type: 'myView'
}
});
var view = Ext.create('MyView', {
renderTo: Ext.getBody()
});
// This will fire the ViewModel formula
//view.getViewModel().set('Something', window.Something);
console.log(Something, window.Something)
}
});
You can workout some logic to handle when Something.Test is not available, something like:
data: {
Something: window.Something && window.Something.Test || {Test: null}
},
formulas: {
testSomething: function(get) {
var val = get('Something.Test');
console.log('Test');
return val;
},
myTitle: function(getter) {
return 'My Title';
}
}
Related
I have Uncaught Type Error : UserRegisterView is not a constructor.I dont understand this error.I looked all code but i dont find it.
Sorry of my bad english.Please help me
Thanks for answer
UPDATED
UserRegisterView is here
var UserRegisterView = Backbone.View.extend({
model: User,
el: '#form',
events: {
'click input[id="infoWeek"]': 'infoWeek',
'click input[id="infoMonth"]': 'infoMonth'
},
infoWeek: function() {
this.$el.find("#dayOfMonth").hide();
this.render();
},
infoMonth: function() {
this.$el.find("#dayOfWeek").hide();
this.render();
}
});
var AddUserView = Backbone.View.extend({
el: $(".page"),
events: {
'click #saveUser': 'saveUser'
},
saveUser: function() {
var user = new User();
user.set({
username: $("#username").val(),
lastName: $("#lastName").val(),
regNumber: $("#regNumber").val(),
password: $("#password").val(),
departmentName: $("#departmentName").val(),
email: $("#email").val(),
role: $("#role").val()
});
user.save();
if (document.getElementById('isOpen').checked) {
user.set("isOpen", $("#isOpen").val("1"));
user.save();
} else {
user.set("isOpen", $("#isOpen").val("0"));
user.save();
}
if (document.getElementById('dayOfWeek').checked) {
user.set("dayOfWeek", $("#dayOfWeek").val());
user.save();
} else if (document.getElementById('dayOfMonth').checked) {
user.set("dayOfMonth", $("#dayOfMonth").val());
user.save();
}
$("#username").val("");
$("#firstName").val("");
$("#lastName").val("");
$("#regNumber").val("");
$("#password").val("");
$("#deparmentName").val("");
$("#email").val("");
$("#isOpen").val("");
$("#dayOfWeek").val("");
$("#dayOfMonth").val("");
},
render: function() {
var that = this;
var template = Handlebars.compile(UserRegister);
var myHtml = template(that.model.toJSON());
that.$el.html(myHtml);
return this;
}
});
return {
AddUserView: AddUserView,
UserRegisterView: UserRegisterView
};
});
router user func.
define([
'jquery',
'underscore',
'backbone',
'handlebars',
'spin',
'app/models/LoginModel',
'app/views/LoginView',
'app/views/UserRegisterView'
], function($,
_,
Backbone,
Handlebars,
Spinner,
Login,
LoginView,
UserRegisterView
) {
var Router = Backbone.Router.extend({
routes: {
'search': 'search',
'login': 'login',
'travels': 'travels',
'user': 'user',
'menu': 'menu',
'': 'home'
},
user: function() {
disposeView(new UserRegisterView().render());
}
dispose.view on util.js
function disposeView(view) {
Backbone.View.prototype.close = function() {
this.unbind();
this.undelegateEvents();
};
/* Şu anki viewi yok et */
if (this.currentView !== undefined) {
this.currentView.close();
}
/* Yeni view oluştur. */
this.currentView = view;
this.currentView.delegateEvents();
return this.currentView;
}
What's happening
Your UserRegisterView module returns an object which contains two constructors.
return {
AddUserView: AddUserView,
UserRegisterView: UserRegisterView
};
When using this module, what you're getting is the object above.
define([
// ...
'app/views/UserRegisterView'
], function(
// ...
UserRegisterView // value of the return in the module
) {
So you're kind of misleading yourself by calling it UserRegisterView as it's not the constructor, but the object containing the constructor.
To get a new UserRegisterView view instance with the current way your module is setup, you'd need to call it like so:
var userView = new UserRegisterView.UserRegisterView();
Or to create a AddUserView instance:
var addView = new UserRegisterView.AddUserView();
Solutions
Split up the module, one for each view constructor.
Change the name so at least it's not misleading (like UserViewsModule)
Other improvements
That being said, there are other improvements that could be made to your Backbone code.
var UserRegisterView = Backbone.View.extend({
// that's useless (if not used) and not a view property.
// model: User,
// don't use `el` like that, especially when using the view as a shared Constructor
el: '#form',
events: {
'click input[id="infoWeek"]': 'onInfoWeekClick',
'click input[id="infoMonth"]': 'onInfoMonthClick'
},
initialize: function() {
// Cache jQuery object of the view's element
this.$dayOfMonth = this.$("#dayOfMonth");
this.$dayOfMonth = this.$("#dayOfMonth");
// also use the shortcut function instead of `this.$el.find()`
}
onInfoWeekClick: function(e) {
this.$dayOfMonth.hide();
// calling render here is useless unless your using it as a parent
// view, where the child view overrides the render function.
},
onInfoMonthClick: function(e) {
this.$dayOfMonth.hide();
}
});
The disposeView function could be simplified:
function disposeView(view) {
var current = this.currentView;
if (current) current.close();
current = this.currentView = view;
current.delegateEvents();
return current;
}
Don't change the default Backbone view prototype each time the function is called. Instead, add the function once.
_.extend(Backbone.View.prototype, {
close: function() {
this.unbind();
this.undelegateEvents();
},
// any other function you want to add can go here.
});
In another answer, I go into details on how to extend Backbone's core classes with requirejs transparently.
You're already using jQuery, so don't use JavaScript DOM API document.getElementById('isOpen') interspersed with jQuery selectors $('#isOpen').
I made some improvements to the following view. Take the time to create yourself some utility functions (like reset and getValues) to simplify the flow of the code and encapsulate the complexity.
var AddUserView = Backbone.View.extend({
el: $(".page"),
events: {
'click #saveUser': 'saveUser'
},
// compile the template once while creating the view class
template: Handlebars.compile(UserRegister),
// get the selector string out of the code and place them in one place
// easy to change and maintain.
fields: {
username: "#username",
firstName: "#firstName",
lastName: "#lastName",
regNumber: "#regNumber",
password: "#password",
deparmentName: "#deparmentName",
email: "#email",
isOpen: "#isOpen",
dayOfWeek: "#dayOfWeek",
dayOfMonth: "#dayOfMonth",
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
// cache jQuery object of every field once after a render
this.field = _.reduce(this.fields, function(fields, selector, key) {
fields['$' + key] = this.$(selector);
return fields;
}, {}, this);
return this;
},
reset: function() {
// reset all the fields once without repeating code.
_.each(this.field, function($field) {
$field.val("");
});
return this;
},
getValues: function(keys) {
// get the value of multiple fields returned in a nice object
// ready to be sent to a Backbone model.
return _.reduce(keys, function(data, key) {
data[key] = this.field[key].val();
return data;
}, {}, this);
},
saveUser: function() {
var field = this.field,
user = new User(this.getValues([
'username',
'lastName',
'regNumber',
'password',
'departmentName',
'email',
'role',
]));
user.set({ isOpen: field.$isOpen.is(':checked') });
if (field.$dayOfWeek.is(':checked')) {
user.set("dayOfWeek", field.$dayOfWeek.val());
} else if (field.$dayOfMonth.is(':checked')) {
user.set("dayOfMonth", field.$dayOfMonth.val());
}
user.save();
this.reset();
},
});
In the following snippet, you're putting the context (this) into a local variable. I see that a lot and I could say that 90% of the times I see it on Stack Overflow questions, it makes no sense. It clearly screams copy-pasted.
render: function() {
var that = this;
// ...
that.$el.html(myHtml);
return this;
}
Please tell me you see that you're putting this into that, then using that throughout the function, then you still return this?!
Putting the context into a local variable is useful when the object is needed in a dynamically created callback.
render: function() {
var that = this; // this is available here
setTimeout(function() {
// here this is not available.
that.handleCallback();
}, 10);
// here we are in the same context as the first line.
return this;
}
fyi, this is my first Ext JS project and using ExtJS6
I created a main border layout and each view has its own controller.
From the west view controller, how do I call a method in the center controller.
Should I provide more info?
The concept is, one the west border I have a combobox. When the user selects a combobox it loads some stores with data. Now using those stores, I want to call some custom functions I build in the CENTER view controller. The reason they are built in the CENTER view controller is because they affect the grids that are sitting in the CENTER VIEW.
Here is my Main.js
Ext.define('ExtApplication1.view.main.Main', {
extend: 'Ext.container.Container',
xtype: 'app-main',
controller: 'main',
viewModel: {
type: 'main'
},
plugins: 'viewport',
requires: [
'ExtApplication1.view.main.Header',
'ExtApplication1.view.main.Footer',
'ExtApplication1.view.main.Panel',
'ExtApplication1.view.main.MainController',
'ExtApplication1.view.main.MainModel',
'ExtApplication1.view.mainmenulist.mainmenulist',
'ExtApplication1.view.clientdetails.clientdetails'
],
layout: {
type: 'border'
},
items: [{
region: 'center',
xtype: 'clientdetails', //should be maintable
title: 'Main Details Panel',
flex: 1
}, {
xtype: 'appheader',
region: 'north'
}, {
xtype: 'appfooter',
region: 'south'
}, {
region: 'west',
split: true,
collapsible: true,
title: 'Main Menu',
//flex: 2,
xtype: 'mainmenulist' // 'mainmenuv4view' 'mainmenuv3view'
}]
});
Here is my WEST region controller, I want to fire event on combobox selection after two different stores are loaded.
Ext.define('ExtApplication1.view.mainmenulist.mainmenulistController', {
extend: 'Ext.app.ViewController',
alias: 'controller.mainmenulist-mainmenulist',
onComboboxSelect: function (combo, record, eOpts) {
//load position store
//load market store
//calculate
var selectedCID = record.get('ClientID');
var targetGrid = Ext.getCmp('positionsGridID');
var targetStore = targetGrid.getStore();
//load positions store
targetStore.load({
params: {
user: 'xxx',
pw: 'xxx',
cid: selectedCID
},
//load market data
callback: function (records, operation, success) {
var targetGrid2 = Ext.getCmp('marketsGridID');
var targetStore2 = targetGrid2.getStore();
targetStore2.load({
params: {
user: 'stephen',
pw: 'forero',
cid: selectedCID
},
callback: function (records, operation, success) {
//Ext.Msg.alert('Status', 'Changes saved successfully.');
this.fireEvent('doSomething', 'arg1', 'arg2');
//ExtApplication1.app.getController('view.clientdetails.clientdetailsController').onClickCalculate();
}
});
}
});
}
});
here is my controller for CENTER region
Ext.define('ExtApplication1.view.clientdetails.clientdetailsController', {
extend: 'Ext.app.ViewController',
alias: 'controller.clientdetails-clientdetails',
init: function() {
this.listen({
controller: {
'*': { // This selector matches any originating Controller
doSomething: 'doSomething'
}
},
})
},
doSomething: function (param1, param2) {
Ext.Msg.alert('Status', 'Changes saved successfully.');
//alert("Param1: " + param1 + " Param2: " + param2);
},
In ExtJS6 you don't have to use the init function to add the listeners:
First Controller
Ext.define('AwesomeController', {
extend: 'Ext.app.ViewController',
alias: 'controller.awesome',
listen: {
controller: {
'amazing': {
amazingEvent: 'onAmazingEvent'
}
}
},
onAmazingEvent: function(controller, arg1, arg2) {
// ..
}
});
Second Controller
Ext.define('AmazingController', {
extend: 'Ext.app.ViewController',
alias: 'controller.amazing',
fooBar: function() {
// ..
this.fireEvent('amazingEvent', this, arg1, arg2);
}
});
Its recommended to add the event source to the event.
For further information on events read here: https://docs.sencha.com/extjs/6.0/core_concepts/events.html
One approach to doing this is to create a controller event. Emit this event from one controller, and listen for it in another controller. This creates looser coupling than directly calling methods on controllers.
For example:
Ext.define('app.controller.ControllerA', {
someMethod: function(){
this.fireEvent('doSomething', 'arg1', 'arg2');
}
});
Ext.define('app.controller.ControllerB', {
init: function() {
this.listen({
controller: {
'*': { // This selector matches any originating Controller
doSomething: 'doSomething'
}
},
})
},
doSomething: function(param1, param2){
alert("Param1: " + param1 + " Param2: " + param2);
}
});
You can do this in ExtJS 4+ I believe. There might be a better way to do it now with version 6, but I'm not sure.
I have a regular tree with elements. What i want to do is following:
When I reload the page, the selected item must be the same as before ( I select only 1 item in the tree).
For example, when I click on 'Sue Potato' - it is selected and when I refresh the page, it must look the same (be also selected).
I've tried reading some Stateful, Provider, Manager on the Sencha Docs, but I didn't get it.
Controller code:
Ext.define('FirstApp.controller.Main', {
extend: 'Ext.app.Controller',
refs: [
{
ref: 'grid',
selector: 'lesson-grid'
},
{
ref: 'tree',
selector: 'school-tree'
}
],
init: function() {
Ext.state.Manager.setProvider(Ext.create('Ext.state.LocalStorageProvider'));
}
});
Tree code:
Ext.define('FirstApp.view.SchoolTree', {
extend: 'Ext.tree.Panel',
xtype: 'school-tree',
stateful: true,
stateId: 'stateGrid',
stateEvents:['selection'],
constructor: function() {
var that = this;
this.store = Ext.create('FirstApp.store.School');
this.store.on('load', function () {
that.getSelectionModel().select(1, true);
});
this.callParent(arguments);
this.getState = function() {
return that.getSelectionModel().getSelection();
};
this.applyState = function() {
};
}
});
Help would be much appreciated.
This is a working code of the requirement above.I had to get an id of the selected element and then pass it to applyState.
Ext.define('FirstApp.view.SchoolTree', {
extend: 'Ext.tree.Panel',
xtype: 'school-tree',
stateful: true,
stateId: 'stateTree',
stateEvents:['selectionchange'],
constructor: function() {
var that = this;
this.store = Ext.create('FirstApp.store.School');
this.store.on('load', function () {
that.getSelectionModel().select(1);
});
this.callParent(arguments);
},
getState: function() {
return {
'stateTree': this.getSelectionModel().getSelection()[0].getId()
};
},
applyState: function(state) {
var me = this;
this.store.on('load', function(record) {
record = this.getById(state.stateTree);
me.getSelectionModel().select(record);
});
}
});
You stored tree state and set it into State Manager
Ext.state.Manager.setProvider(Ext.create('Ext.state.LocalStorageProvider'));
So when you will refresh your page state will apply and an item will be also selected.
Is there any requirement to store state of tree? If yes and still you don't want to selected then you can forcefully clear selection on tree render.
anybody, please help me with created component for extjs 4.2
Ext.define('mycomponent', {
extend:'Ext.form.field.Display',
alias: 'widget.mycomponent',
initComponent: function() {
this.setValue("some value") // not setup
this.callParent(arguments);
console.log(this)
},
})
i try
Ext.getCmp(this.id).setValue("some")
but html object do not exist, events beforerender e.t.c. not running. how i can set value?
Here's a fully working example, tested with 4.2.1.
Ext.define('Foo', {
extend:'Ext.form.field.Display',
alias: 'widget.mycomponent',
initComponent: function() {
this.setValue("some value") // not setup
this.callParent(arguments);
}
})
Ext.onReady(function() {
new Foo({
renderTo: document.body
})
});
You need to define the getValue() and setValue() methods in your constructor in order to work.
getValue: function () {
var me = this,
value;
return value;
}
setValue: function (value) {
var me = this;
// Example only should't work as it is
me.displayField.setValue(value);
}
Trying to create an inline edit form.
I have a form that looks like this:
var editPic = "<img src='https://s3.amazonaws.com/bzimages/pencil.png' alt='edit' height='24' width='24' style='margin-left: 10px;'/>";
var submitPic = "<img id='submitPic' src='https://s3.amazonaws.com/bzimages/submitPic.png' alt='edit' height='24' width='24'/>";
Ext.define('BM.view.test.Edit', {
extend: 'Ext.form.Panel',
alias: 'widget.test-edit',
layout: 'anchor',
title: 'Edit Test',
defaultType: 'displayfield',
items: [
{name: 'id', hidden: true},
{
name: 'name',
fieldLabel: 'Name',
afterSubTpl: editPic,
cls: 'editable'
},
{
name: 'nameEdit',
fieldLabel: 'Name',
xtype: 'textfield',
hidden: true,
cls: 'editMode',
allowBlank: false,
afterSubTpl: submitPic
}
]
});
The controller looks like this (a lot of events):
init: function() {
this.control({
'test-edit > displayfield': {
afterrender: this.showEditable
},
'test-edit': {
afterrender: this.formRendered
},
'test-edit > field[cls=editMode]': {
specialkey: this.editField,
blur: this.outOfFocus
}
});
},
outOfFocus: function(field, event) {
console.log('Lost focus');
this.revertToDisplayField(field);
},
revertToDisplayField: function(field) {
field.previousNode().show();
field.hide();
},
formRendered: function(form) {
Ext.get('submitPic').on('click', function (event, object) {
var field = Ext.get(object).parent().parent().parent().parent();
var cmp = Ext.ComponentQuery.query('test-edit > field[cls=editMode]');
});
},
editField: function(field, e) {
var value = field.value;
if (e.getKey() === e.ENTER) {
if (!field.allowBlank && Ext.isEmpty(value)){
console.log('Not permitted!');
} else {
var record = Ext.ComponentQuery.query('test-edit')[0].getForm().getRecord();
Ext.Ajax.request({
url: '../webapp/tests/update',
method:'Post',
params: {
id: record.getId(),
fieldName: field.name,
fieldValue: field.value
},
store: record.store,
success: function(response, t){
field.previousNode().setValue(value);
t.store.reload();
var text = response.responseText;
// process server response here
console.log('Update successful!');
}
});
}
this.revertToDisplayField(field);
} else if (e.getKey() === e.ESC) {
console.log('gave up');
this.revertToDisplayField(field);
}
},
showEditable: function(df) {
df.getEl().on("click", handleClick, this, df);
function handleClick(e, t, df){
e.preventDefault();
var editable = df.nextNode();
editable.setValue(df.getValue());
editable.show();
editable.focus();
df.hide();
}
},
I'm using the 'afterSubTpl' config to add the edit icon, and the accept icon.
I have listeners set up to listen on click events concerning them, but after they are clicked, I only have the element created by Ext.get('submitPic'). Now I want to have access to the the Ext field and form that surround it. The parent method only brings back other DOM elements. How do I connect between them? You can see what I tried in formRendered.
I hope someone can clarify this little bit for me.
Walk up the DOM tree until you find a component for the element's id:
getCmpFromEl = function(el) {
var body = Ext.getBody();
var cmp;
do {
cmp = Ext.getCmp(el.id);
el = el.parentNode;
} while (!cmp && el !== body);
return cmp;
}
Ext.Component.from(el) does exactly this since ExtJS 6.5.0, as I just learnt. Doc
Source
You can get the component by id, but only if your component and its dom element have the same id (they usually do):
Ext.getCmp(yourelement.id)
But this is not exactly good practice -- it would be better to set up your listeners so that the handler methods already have a reference to the component. For example, in your 'submitPic' component, you could define the click listener like this:
var me = this;
me.on({
click: function(arguments){
var cmp = me;
...
});