Prevent memory leaks in *Model

This commit is contained in:
djmaze 2020-10-25 11:46:58 +01:00
parent 48a9a03762
commit d7a4639d6b
22 changed files with 221 additions and 225 deletions

View file

@ -5,50 +5,52 @@ function disposeOne(disposable) {
} }
} }
function typeCast(current, value) { function typeCast(curValue, newValue) {
switch (typeof current) switch (typeof curValue)
{ {
case 'boolean': return !!value; case 'boolean': return !!newValue;
case 'number': return isFinite(value) ? parseFloat(value) : 0; case 'number': return isFinite(newValue) ? parseFloat(newValue) : 0;
case 'string': return null != value ? '' + value : ''; case 'string': return null != newValue ? '' + newValue : '';
case 'object': case 'object':
if (current.constructor.reviveFromJson) { if (curValue.constructor.reviveFromJson) {
return current.constructor.reviveFromJson(value) || undefined; return curValue.constructor.reviveFromJson(newValue) || undefined;
} }
if (!Array.isArray(current) || !Array.isArray(value)) if (!Array.isArray(curValue) || !Array.isArray(newValue))
return undefined; return undefined;
} }
return value; return newValue;
} }
export class AbstractModel { export class AbstractModel {
disposables = []; disposables = [];
/**
* @param {string} modelName = ''
*/
constructor() { constructor() {
/* /*
if (new.target === AbstractModel) { if (new.target === AbstractModel) {
throw new Error("Can't instantiate AbstractModel!"); throw new Error("Can't instantiate AbstractModel!");
} }
this.sModelName = new.target.name;
*/ */
} }
regDisposables(value) { addObservables(obj) {
if (Array.isArray(value)) { Object.entries(obj).forEach(([key, value]) => this[key] = ko.observable(value) );
value.forEach(item => this.disposables.push(item)); /*
} else if (value) { Object.entries(obj).forEach(([key, value]) =>
this.disposables.push(value); this[key] = Array.isArray(value) ? ko.observableArray(value) : ko.observable(value)
);
*/
} }
addSubscribables(obj) {
Object.entries(obj).forEach(([key, fn]) => this.disposables.push( this[key].subscribe(fn) ) );
} }
onDestroy() { onDestroy() {
if (Array.isArray(this.disposables)) {
this.disposables.forEach(disposeOne); this.disposables.forEach(disposeOne);
} Object.entries(this).forEach(([key, value]) => {
Object.values(this).forEach(disposeOne); disposeOne(value);
this[key] = null;
});
} }
/** /**

View file

@ -1,5 +1,3 @@
import ko from 'ko';
import { change } from 'Common/Links'; import { change } from 'Common/Links';
import { AbstractModel } from 'Knoin/AbstractModel'; import { AbstractModel } from 'Knoin/AbstractModel';
@ -15,10 +13,11 @@ class AccountModel extends AbstractModel {
this.email = email; this.email = email;
this.count = ko.observable(count); this.addObservables({
count: count,
this.deleteAccess = ko.observable(false); deleteAccess: false,
this.canBeDeleted = ko.observable(!!canBeDelete); canBeDeleted: !!canBeDelete
});
this.canBeEdit = this.canBeDeleted; this.canBeEdit = this.canBeDeleted;
} }

View file

@ -24,16 +24,18 @@ class ComposeAttachmentModel extends AbstractModel {
this.contentLocation = contentLocation; this.contentLocation = contentLocation;
this.fromMessage = false; this.fromMessage = false;
this.fileName = ko.observable(fileName); this.addObservables({
this.size = ko.observable(size); fileName: fileName,
this.tempName = ko.observable(''); size: size,
tempName: '',
this.progress = ko.observable(0); progress: 0,
this.error = ko.observable(''); error: '',
this.waiting = ko.observable(true); waiting: true,
this.uploading = ko.observable(false); uploading: false,
this.enabled = ko.observable(true); enabled: true,
this.complete = ko.observable(false); complete: false
});
this.progressText = ko.computed(() => { this.progressText = ko.computed(() => {
const p = this.progress(); const p = this.progress();
@ -56,15 +58,6 @@ class ComposeAttachmentModel extends AbstractModel {
this.mimeType = ko.computed(() => File.getContentType(this.fileName())); this.mimeType = ko.computed(() => File.getContentType(this.fileName()));
this.fileExt = ko.computed(() => File.getExtension(this.fileName())); this.fileExt = ko.computed(() => File.getExtension(this.fileName()));
this.regDisposables([
this.progressText,
this.progressStyle,
this.title,
this.friendlySize,
this.mimeType,
this.fileExt
]);
} }
static fromAttachment(item) static fromAttachment(item)

View file

@ -1,5 +1,3 @@
import ko from 'ko';
import { ContactPropertyModel } from 'Model/ContactProperty'; import { ContactPropertyModel } from 'Model/ContactProperty';
import { ContactPropertyType } from 'Common/Enums'; import { ContactPropertyType } from 'Common/Enums';
@ -14,10 +12,12 @@ class ContactModel extends AbstractModel {
this.properties = []; this.properties = [];
this.readOnly = false; this.readOnly = false;
this.focused = ko.observable(false); this.addObservables({
this.selected = ko.observable(false); focused: false,
this.checked = ko.observable(false); selected: false,
this.deleted = ko.observable(false); checked: false,
deleted: false
});
} }
/** /**

View file

@ -17,12 +17,14 @@ class ContactPropertyModel extends AbstractModel {
constructor(type = ContactPropertyType.Unknown, typeStr = '', value = '', focused = false, placeholder = '') { constructor(type = ContactPropertyType.Unknown, typeStr = '', value = '', focused = false, placeholder = '') {
super(); super();
this.type = ko.observable(pInt(type)); this.addObservables({
this.typeStr = ko.observable(pString(typeStr)); type: pInt(type),
this.focused = ko.observable(!!focused); typeStr: pString(typeStr),
this.value = ko.observable(pString(value)); focused: !!focused,
value: pString(value),
this.placeholder = ko.observable(placeholder); placeholder: placeholder
});
this.placeholderValue = ko.computed(() => { this.placeholderValue = ko.computed(() => {
const v = this.placeholder(); const v = this.placeholder();
@ -30,8 +32,6 @@ class ContactPropertyModel extends AbstractModel {
}); });
this.largeValue = ko.computed(() => ContactPropertyType.Note === this.type()); this.largeValue = ko.computed(() => ContactPropertyType.Note === this.type());
this.regDisposables([this.placeholderValue, this.largeValue]);
} }
toJSON() { toJSON() {

View file

@ -15,43 +15,39 @@ class FilterModel extends AbstractModel {
constructor() { constructor() {
super(); super();
this.enabled = ko.observable(true);
this.id = ''; this.id = '';
this.name = ko.observable(''); this.addObservables({
this.name.error = ko.observable(false); enabled: true,
this.name.focused = ko.observable(false); deleteAccess: false,
canBeDeleted: true,
this.conditions = ko.observableArray([]); name: '',
this.conditionsType = ko.observable(FilterRulesType.Any); nameError: false,
nameFocused: false,
conditionsType: FilterRulesType.Any,
// Actions // Actions
this.actionValue = ko.observable(''); actionValue: '',
this.actionValue.error = ko.observable(false); actionValueError: false,
this.actionValueSecond = ko.observable(''); actionValueSecond: '',
this.actionValueThird = ko.observable(''); actionValueThird: '',
this.actionValueFourth = ko.observable(''); actionValueFourth: '',
this.actionValueFourth.error = ko.observable(false); actionValueFourthError: false,
this.actionMarkAsRead = ko.observable(false); actionMarkAsRead: false,
this.actionKeep = ko.observable(true); actionKeep: true,
this.actionNoStop = ko.observable(false); actionNoStop: false,
this.actionType = ko.observable(FiltersAction.MoveTo); actionType: FiltersAction.MoveTo
this.actionType.subscribe(() => {
this.actionValue('');
this.actionValue.error(false);
this.actionValueSecond('');
this.actionValueThird('');
this.actionValueFourth('');
this.actionValueFourth.error(false);
}); });
this.conditions = ko.observableArray([]);
const fGetRealFolderName = (folderFullNameRaw) => { const fGetRealFolderName = (folderFullNameRaw) => {
const folder = getFolderFromCacheList(folderFullNameRaw); const folder = getFolderFromCacheList(folderFullNameRaw);
return folder ? folder.fullName.replace('.' === folder.delimiter ? /\./ : /[\\/]+/, ' / ') : folderFullNameRaw; return folder ? folder.fullName.replace('.' === folder.delimiter ? /\./ : /[\\/]+/, ' / ') : folderFullNameRaw;
@ -115,14 +111,18 @@ class FilterModel extends AbstractModel {
return result; return result;
}); });
this.regDisposables(this.name.subscribe(sValue => this.name.error(!sValue))); this.addSubscribables({
name: sValue => this.nameError(!sValue),
this.regDisposables(this.actionValue.subscribe(sValue => this.actionValue.error(!sValue))); actionValue: sValue => this.actionValueError(!sValue),
actionType: () => {
this.regDisposables([this.actionNoStop, this.actionTemplate]); this.actionValue('');
this.actionValueError(false);
this.deleteAccess = ko.observable(false); this.actionValueSecond('');
this.canBeDeleted = ko.observable(true); this.actionValueThird('');
this.actionValueFourth('');
this.actionValueFourthError(false);
}
});
} }
generateID() { generateID() {
@ -131,15 +131,13 @@ class FilterModel extends AbstractModel {
verify() { verify() {
if (!this.name()) { if (!this.name()) {
this.name.error(true); this.nameError(true);
return false; return false;
} }
if (this.conditions().length) { if (this.conditions().length && this.conditions().find(cond => cond && !cond.verify())) {
if (this.conditions().find(cond => cond && !cond.verify())) {
return false; return false;
} }
}
if (!this.actionValue()) { if (!this.actionValue()) {
if ([ if ([
@ -149,13 +147,13 @@ class FilterModel extends AbstractModel {
FiltersAction.Vacation FiltersAction.Vacation
].includes(this.actionType()) ].includes(this.actionType())
) { ) {
this.actionValue.error(true); this.actionValueError(true);
return false; return false;
} }
} }
if (FiltersAction.Forward === this.actionType() && !this.actionValue().includes('@')) { if (FiltersAction.Forward === this.actionType() && !this.actionValue().includes('@')) {
this.actionValue.error(true); this.actionValueError(true);
return false; return false;
} }
@ -164,12 +162,12 @@ class FilterModel extends AbstractModel {
this.actionValueFourth() && this.actionValueFourth() &&
!this.actionValueFourth().includes('@') !this.actionValueFourth().includes('@')
) { ) {
this.actionValueFourth.error(true); this.actionValueFourthError(true);
return false; return false;
} }
this.name.error(false); this.nameError(false);
this.actionValue.error(false); this.actionValueError(false);
return true; return true;
} }
@ -240,7 +238,7 @@ class FilterModel extends AbstractModel {
filter.enabled(this.enabled()); filter.enabled(this.enabled());
filter.name(this.name()); filter.name(this.name());
filter.name.error(this.name.error()); filter.nameError(this.nameError());
filter.conditionsType(this.conditionsType()); filter.conditionsType(this.conditionsType());
@ -249,7 +247,7 @@ class FilterModel extends AbstractModel {
filter.actionType(this.actionType()); filter.actionType(this.actionType());
filter.actionValue(this.actionValue()); filter.actionValue(this.actionValue());
filter.actionValue.error(this.actionValue.error()); filter.actionValueError(this.actionValueError());
filter.actionValueSecond(this.actionValueSecond()); filter.actionValueSecond(this.actionValueSecond());
filter.actionValueThird(this.actionValueThird()); filter.actionValueThird(this.actionValueThird());

View file

@ -8,13 +8,15 @@ class FilterConditionModel extends AbstractModel {
constructor() { constructor() {
super(); super();
this.field = ko.observable(FilterConditionField.From); this.addObservables({
this.type = ko.observable(FilterConditionType.Contains); field: FilterConditionField.From,
this.value = ko.observable(''); type: FilterConditionType.Contains,
this.value.error = ko.observable(false); value: '',
valueError: false,
this.valueSecond = ko.observable(''); valueSecond: '',
this.valueSecond.error = ko.observable(false); valueSecondError: false
});
this.template = ko.computed(() => { this.template = ko.computed(() => {
let template = ''; let template = '';
@ -33,22 +35,22 @@ class FilterConditionModel extends AbstractModel {
return template; return template;
}, this); }, this);
this.field.subscribe(() => { this.addSubscribables({
field: () => {
this.value(''); this.value('');
this.valueSecond(''); this.valueSecond('');
}
}); });
this.regDisposables([this.template]);
} }
verify() { verify() {
if (!this.value()) { if (!this.value()) {
this.value.error(true); this.valueError(true);
return false; return false;
} }
if (FilterConditionField.Header === this.field() && !this.valueSecond()) { if (FilterConditionField.Header === this.field() && !this.valueSecond()) {
this.valueSecond.error(true); this.valueSecondError(true);
return false; return false;
} }

View file

@ -11,7 +11,6 @@ class FolderModel extends AbstractModel {
constructor() { constructor() {
super(); super();
this.name = ko.observable('');
this.fullName = ''; this.fullName = '';
this.fullNameRaw = ''; this.fullNameRaw = '';
this.fullNameHash = ''; this.fullNameHash = '';
@ -23,23 +22,27 @@ class FolderModel extends AbstractModel {
this.selectable = false; this.selectable = false;
this.existen = true; this.existen = true;
this.type = ko.observable(FolderType.User); this.addObservables({
name: '',
type: FolderType.User,
focused: false,
selected: false,
edited: false,
subScribed: true,
checkable: false,
deleteAccess: false,
nameForEdit: '',
privateMessageCountAll: 0,
privateMessageCountUnread: 0,
collapsedPrivate: true
});
this.focused = ko.observable(false);
this.selected = ko.observable(false);
this.edited = ko.observable(false);
this.subScribed = ko.observable(true);
this.checkable = ko.observable(false);
this.subFolders = ko.observableArray(new FolderCollectionModel); this.subFolders = ko.observableArray(new FolderCollectionModel);
this.deleteAccess = ko.observable(false);
this.actionBlink = ko.observable(false).extend({ falseTimeout: 1000 }); this.actionBlink = ko.observable(false).extend({ falseTimeout: 1000 });
this.nameForEdit = ko.observable('');
this.privateMessageCountAll = ko.observable(0);
this.privateMessageCountUnread = ko.observable(0);
this.collapsedPrivate = ko.observable(true);
} }
/** /**
@ -230,15 +233,16 @@ class FolderModel extends AbstractModel {
) )
); );
// subscribe folder.addSubscribables({
folder.name.subscribe(value => folder.nameForEdit(value)); name: value => folder.nameForEdit(value),
folder.edited.subscribe(value => value && folder.nameForEdit(folder.name())); edited: value => value && folder.nameForEdit(folder.name()),
folder.messageCountUnread.subscribe((unread) => { messageCountUnread: unread => {
if (FolderType.Inbox === folder.type()) { if (FolderType.Inbox === folder.type()) {
dispatchEvent(new CustomEvent('mailbox.inbox-unread-count', {detail:unread})); dispatchEvent(new CustomEvent('mailbox.inbox-unread-count', {detail:unread}));
} }
}
}); });
} }
return folder; return folder;

View file

@ -10,17 +10,20 @@ class IdentityModel extends AbstractModel {
constructor(id, email) { constructor(id, email) {
super(); super();
this.id = ko.observable(id || ''); this.addObservables({
this.email = ko.observable(email); id: id || '',
this.name = ko.observable(''); email: email,
name: '',
this.replyTo = ko.observable(''); replyTo: '',
this.bcc = ko.observable(''); bcc: '',
this.signature = ko.observable(''); signature: '',
this.signatureInsertBefore = ko.observable(false); signatureInsertBefore: false,
deleteAccess: false
});
this.deleteAccess = ko.observable(false);
this.canBeDeleted = ko.computed(() => !!this.id()); this.canBeDeleted = ko.computed(() => !!this.id());
} }

View file

@ -31,57 +31,56 @@ class MessageModel extends AbstractModel {
this._reset(); this._reset();
this.subject = ko.observable(''); this.addObservables({
this.subjectPrefix = ko.observable(''); subject: '',
this.subjectSuffix = ko.observable(''); subjectPrefix: '',
this.size = ko.observable(0); subjectSuffix: '',
this.dateTimeStampInUTC = ko.observable(0); size: 0,
this.priority = ko.observable(MessagePriority.Normal); dateTimeStampInUTC: 0,
priority: MessagePriority.Normal,
this.senderEmailsString = ko.observable(''); senderEmailsString: '',
this.senderClearEmailsString = ko.observable(''); senderClearEmailsString: '',
this.newForAnimation = ko.observable(false); newForAnimation: false,
this.deleted = ko.observable(false); deleted: false,
this.isDeleted = ko.observable(false); isDeleted: false,
this.isUnseen = ko.observable(false); isUnseen: false,
this.isFlagged = ko.observable(false); isFlagged: false,
this.isAnswered = ko.observable(false); isAnswered: false,
this.isForwarded = ko.observable(false); isForwarded: false,
this.isReadReceipt = ko.observable(false); isReadReceipt: false,
this.focused = ko.observable(false); focused: false,
this.selected = ko.observable(false); selected: false,
this.checked = ko.observable(false); checked: false,
this.hasAttachments = ko.observable(false); hasAttachments: false,
this.attachmentsSpecData = ko.observableArray([]);
isHtml: false,
hasImages: false,
isPgpSigned: false,
isPgpEncrypted: false,
pgpSignedVerifyStatus: SignedVerifyStatus.None,
pgpSignedVerifyUser: '',
readReceipt: '',
hasUnseenSubMessage: false,
hasFlaggedSubMessage: false
});
this.attachmentIconClass = ko.computed(() => this.attachmentIconClass = ko.computed(() =>
File.getCombinedIconClass(this.hasAttachments() ? this.attachmentsSpecData() : []) File.getCombinedIconClass(this.hasAttachments() ? this.attachmentsSpecData() : [])
); );
this.isHtml = ko.observable(false); this.attachments = ko.observableArray(new AttachmentCollectionModel);
this.hasImages = ko.observable(false); this.attachmentsSpecData = ko.observableArray([]);
this.attachments = ko.observable(new AttachmentCollectionModel);
this.isPgpSigned = ko.observable(false);
this.isPgpEncrypted = ko.observable(false);
this.pgpSignedVerifyStatus = ko.observable(SignedVerifyStatus.None);
this.pgpSignedVerifyUser = ko.observable('');
this.priority = ko.observable(MessagePriority.Normal);
this.readReceipt = ko.observable('');
this.hasUnseenSubMessage = ko.observable(false);
this.hasFlaggedSubMessage = ko.observable(false);
this.threads = ko.observableArray([]); this.threads = ko.observableArray([]);
this.threadsLen = ko.computed(() => this.threads().length); this.threadsLen = ko.computed(() => this.threads().length);
this.isImportant = ko.computed(() => MessagePriority.High === this.priority()); this.isImportant = ko.computed(() => MessagePriority.High === this.priority());
this.regDisposables([this.attachmentIconClass, this.threadsLen, this.isImportant]);
} }
_reset() { _reset() {

View file

@ -55,10 +55,9 @@ class ContactsPopupView extends AbstractViewNext {
this.importUploaderButton = ko.observable(null); this.importUploaderButton = ko.observable(null);
this.contactsPage = ko.observable(1); this.contactsPage = ko.observable(1);
this.contactsPageCount = ko.computed(() => { this.contactsPageCount = ko.computed(() =>
const iPage = Math.ceil(this.contactsCount() / CONTACTS_PER_PAGE); Math.max(1, Math.ceil(this.contactsCount() / CONTACTS_PER_PAGE))
return 0 >= iPage ? 1 : iPage; );
});
this.contactsPaginator = ko.computed(computedPaginatorHelper(this.contactsPage, this.contactsPageCount)); this.contactsPaginator = ko.computed(computedPaginatorHelper(this.contactsPage, this.contactsPageCount));
@ -104,10 +103,7 @@ class ContactsPopupView extends AbstractViewNext {
this.viewPropertiesNames().filter(property => !!trim(property.value())) this.viewPropertiesNames().filter(property => !!trim(property.value()))
); );
const propertyFocused = (property) => { const propertyFocused = property => !trim(property.value()) && !property.focused();
const focused = property.focused();
return !trim(property.value()) && !focused;
};
this.viewPropertiesEmailsEmptyAndOnFocused = ko.computed(() => this.viewPropertiesEmailsEmptyAndOnFocused = ko.computed(() =>
this.viewPropertiesEmails().filter(propertyFocused) this.viewPropertiesEmails().filter(propertyFocused)
@ -117,7 +113,9 @@ class ContactsPopupView extends AbstractViewNext {
this.viewPropertiesPhones().filter(propertyFocused) this.viewPropertiesPhones().filter(propertyFocused)
); );
this.viewPropertiesWebEmptyAndOnFocused = ko.computed(() => this.viewPropertiesWeb().filter(propertyFocused)); this.viewPropertiesWebEmptyAndOnFocused = ko.computed(() =>
this.viewPropertiesWeb().filter(propertyFocused)
);
this.viewPropertiesOtherEmptyAndOnFocused = ko.computed(() => this.viewPropertiesOtherEmptyAndOnFocused = ko.computed(() =>
this.viewPropertiesOther().filter(propertyFocused) this.viewPropertiesOther().filter(propertyFocused)
@ -141,9 +139,7 @@ class ContactsPopupView extends AbstractViewNext {
this.useCheckboxesInList = SettingsStore.useCheckboxesInList; this.useCheckboxesInList = SettingsStore.useCheckboxesInList;
this.search.subscribe(() => { this.search.subscribe(() => this.reloadContactList());
this.reloadContactList();
});
this.contactsChecked = ko.computed(() => this.contacts().filter(item => item.checked())); this.contactsChecked = ko.computed(() => this.contacts().filter(item => item.checked()));
@ -170,14 +166,14 @@ class ContactsPopupView extends AbstractViewNext {
'.e-contact-item.focused' '.e-contact-item.focused'
); );
this.selector.on('onItemSelect', (contact) => { this.selector.on('onItemSelect', contact => {
this.populateViewContact(contact ? contact : null); this.populateViewContact(contact || null);
if (!contact) { if (!contact) {
this.emptySelection(true); this.emptySelection(true);
} }
}); });
this.selector.on('onItemGetUid', (contact) => (contact ? contact.generateUid() : '')); this.selector.on('onItemGetUid', contact => contact ? contact.generateUid() : '');
this.bDropPageAfterDelete = false; this.bDropPageAfterDelete = false;

View file

@ -33,7 +33,7 @@ class FilterPopupView extends AbstractViewNext {
this.selectedFolderValue.subscribe(() => { this.selectedFolderValue.subscribe(() => {
if (this.filter()) { if (this.filter()) {
this.filter().actionValue.error(false); this.filter().actionValueError(false);
} }
}); });
@ -157,13 +157,13 @@ class FilterPopupView extends AbstractViewNext {
this.isNew(!bEdit); this.isNew(!bEdit);
if (!bEdit && oFilter) { if (!bEdit && oFilter) {
oFilter.name.focused(true); oFilter.nameFocused(true);
} }
} }
onShowWithDelay() { onShowWithDelay() {
if (this.isNew() && this.filter()/* && !rl.settings.app('mobile')*/) { if (this.isNew() && this.filter()/* && !rl.settings.app('mobile')*/) {
this.filter().name.focused(true); this.filter().nameFocused(true);
} }
} }
} }

View file

@ -11,10 +11,10 @@
<div class="row filter" data-bind="with: filter, i18nInit: filter"> <div class="row filter" data-bind="with: filter, i18nInit: filter">
<div class="span9" data-bind="i18nInit: true"> <div class="span9" data-bind="i18nInit: true">
<div class="control-group" data-bind="css: {'error': name.error}"> <div class="control-group" data-bind="css: {'error': nameError}">
<div class="controls"> <div class="controls">
<input type="text" class="i18n span5" <input type="text" class="i18n span5"
data-bind="value: name, hasFocus: name.focused" data-bind="value: name, hasFocus: nameFocused"
autocorrect="off" autocapitalize="off" spellcheck="false" autocorrect="off" autocapitalize="off" spellcheck="false"
data-i18n="[placeholder]POPUPS_FILTER/FILTER_NAME" data-i18n="[placeholder]POPUPS_FILTER/FILTER_NAME"
/> />

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': actionValueError}" style="margin-bottom: 0">
<div class="controls"> <div class="controls">
<div data-bind="component: { <div data-bind="component: {
name: 'Checkbox', name: 'Checkbox',

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}"> <div class="control-group" data-bind="css: {'error': actionValueError}">
<div class="controls"> <div class="controls">
<input type="text" class="span3 i18n" data-bind="value: actionValue" <input type="text" class="span3 i18n" data-bind="value: actionValue"
data-i18n="[placeholder]POPUPS_FILTER/EMAIL_LABEL" /> data-i18n="[placeholder]POPUPS_FILTER/EMAIL_LABEL" />

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}"> <div class="control-group" data-bind="css: {'error': actionValueError}">
<div class="controls"> <div class="controls">
<select class="span3" data-bind="options: $root.folderSelectList, value: $root.selectedFolderValue, <select class="span3" data-bind="options: $root.folderSelectList, value: $root.selectedFolderValue,
optionsText: 'name', optionsValue: 'id', optionsAfterRender: $root.defaultOptionsAfterRender"></select> optionsText: 'name', optionsValue: 'id', optionsAfterRender: $root.defaultOptionsAfterRender"></select>

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': actionValueError}" style="margin-bottom: 0">
<div class="controls"> <div class="controls">
<div data-bind="component: { <div data-bind="component: {
name: 'Checkbox', name: 'Checkbox',

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': actionValueError}" style="margin-bottom: 0">
<div class="controls"> <div class="controls">
<textarea class="span5 i18n" data-bind="value: actionValue" style="height: 100px;" <textarea class="span5 i18n" data-bind="value: actionValue" style="height: 100px;"
data-i18n="[placeholder]POPUPS_FILTER/REJECT_MESSAGE_LABEL"></textarea> data-i18n="[placeholder]POPUPS_FILTER/REJECT_MESSAGE_LABEL"></textarea>

View file

@ -11,7 +11,7 @@
}"></div> }"></div>
</div> </div>
</div> </div>
<div class="control-group" data-bind="css: {'error': actionValueFourth.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': actionValueFourthError}" style="margin-bottom: 0">
<div class="controls"> <div class="controls">
<input type="text" class="span5 i18n" data-bind="value: actionValueFourth" <input type="text" class="span5 i18n" data-bind="value: actionValueFourth"
data-i18n="[placeholder]POPUPS_FILTER/VACATION_RECIPIENTS_LABEL" /> data-i18n="[placeholder]POPUPS_FILTER/VACATION_RECIPIENTS_LABEL" />
@ -26,7 +26,7 @@
data-i18n="[placeholder]POPUPS_FILTER/VACATION_SUBJECT_LABEL" /> data-i18n="[placeholder]POPUPS_FILTER/VACATION_SUBJECT_LABEL" />
</div> </div>
</div> </div>
<div class="control-group" data-bind="css: {'error': actionValue.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': actionValueError}" style="margin-bottom: 0">
<div class="controls"> <div class="controls">
<textarea class="span5 i18n" data-bind="value: actionValue" style="height: 100px;" <textarea class="span5 i18n" data-bind="value: actionValue" style="height: 100px;"
data-i18n="[placeholder]POPUPS_FILTER/VACATION_MESSAGE_LABEL"></textarea> data-i18n="[placeholder]POPUPS_FILTER/VACATION_MESSAGE_LABEL"></textarea>

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': value.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': valueError}" style="margin-bottom: 0">
<select class="span3" data-bind="options: $root.fieldOptions, value: field, optionsText: 'name', optionsValue: 'id'"></select> <select class="span3" data-bind="options: $root.fieldOptions, value: field, optionsText: 'name', optionsValue: 'id'"></select>
&nbsp; &nbsp;
<select class="span2" data-bind="options: $root.typeOptions, value: type, optionsText: 'name', optionsValue: 'id'"></select> <select class="span2" data-bind="options: $root.typeOptions, value: type, optionsText: 'name', optionsValue: 'id'"></select>

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': value.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': valueError}" style="margin-bottom: 0">
<select class="span2" data-bind="options: $root.fieldOptions, value: field, optionsText: 'name', optionsValue: 'id'"></select> <select class="span2" data-bind="options: $root.fieldOptions, value: field, optionsText: 'name', optionsValue: 'id'"></select>
&nbsp; &nbsp;
<input class="span2" type="text" data-bind="value: valueSecond" /> <input class="span2" type="text" data-bind="value: valueSecond" />

View file

@ -1,4 +1,4 @@
<div class="control-group" data-bind="css: {'error': value.error}" style="margin-bottom: 0"> <div class="control-group" data-bind="css: {'error': valueError}" style="margin-bottom: 0">
<select class="span3" data-bind="options: $root.fieldOptions, value: field, optionsText: 'name', optionsValue: 'id'"></select> <select class="span3" data-bind="options: $root.fieldOptions, value: field, optionsText: 'name', optionsValue: 'id'"></select>
&nbsp; &nbsp;
<select class="span2" data-bind="options: $root.typeOptionsSize, value: type, optionsText: 'name', optionsValue: 'id'"></select> <select class="span2" data-bind="options: $root.typeOptionsSize, value: type, optionsText: 'name', optionsValue: 'id'"></select>