Visualforce для аутистов

Visualforce для аутистов

Господа, позвольте в очередной раз разбавить ваши интеллектуальные диспуты дурацким вопросом.
Как. Мне. Понять. Visualforce? Т.е. вы, конечно, можете ответить: "читай workbook_vf, кретин", но не нужно этого делать, потому что я читал. Читал и повторял. Но мне так и не удалось понять Суть! Вроде всё просто - считай HTML только с другими тегами, но вот не получается никак. Дух машины выдает мне раз за разом одно и тоже надменное:
"не удалось разрешить объект из связывания значения <apex:outputField> ({!pitem.Description__c}). <apex:outputField> может использоваться только с объектами sObject или объектами, которые разрешают компонент поля Visualforce."
Может есть какой-то иной источник мудрости по этому вопросу, а гугл скрывает его от меня? Может найдётся славный парень, который пояснит мне на пальцах, как делать простейшие вещи в visualforce?
А то у меня не идёт дело дальше вывода продуктов и цен на страницу. Это ведь обидно, я же считал, что уж что-что, а с вижлфорсом у меня трудностей вообще не возникнет.
Помогите, прошу вас!

Sebastian, твои непонимания Visualforce не совсем понятны :). Что конкретно вызывает у тебя трудности. Согласен, подход в Visualforce немного сложнее чем у чистого html, возможно есть какая-то магия, про которую надо знать. Но вот на мой взгляд, взаимодействие между страницей(client) и контроллером(server) намного проще чем в других языках программирования.
Я предлагаю тебе поставить перед собой конкретную практическую задачу, которую ты знаешь как реализовать на других платформах и попытаться ее сделать на Salesforce.
И все вопросы по ходу выполнения этой задачи пиши здесь, будем помогать.
А так твой вопрос слишком обширный, чтобы на него дать ответ.

Dmitry Shnyrev
Я предлагаю тебе поставить перед собой конкретную практическую задачу, которую ты знаешь как реализовать на других платформах и попытаться ее сделать на Salesforce.
И все вопросы по ходу выполнения этой задачи пиши здесь, будем помогать.
А так твой вопрос слишком обширный, чтобы на него дать ответ.

Да задача-то у меня простейшая, базовая, я бы сказал: создать страницу для списка, к примеру, товаров: имя, описание, цена (вообще неважно), да чтобы с на этой странице была возможность добавлять новые товары. Мне казалось, что всё будет проще, сделаю как в HTML: input, button, value, submit - проблема решена. Но что-то вот не получается нифига. А в workbook_vf вроде как всё разжёвано, что проще некуда, да вот нужного только нет.
Если конкретно, то вот такие непонятности:
1. Неужто для того, чтобы создать такую "сложную" страницу не хватает функционала стандартного контроллера?
2. Почему, запиленная мною кнопка {!save} вместо того, чтобы порадовать меня созданием нового объекта прям со страницы, выкидывает на домашнюю? Или без отдельного специально написанного контроллера это невозможно? Это ведь совсем простая задача.
3. Очевидно, что у меня где-то в самом начале имеется какая-то фундаментальная ошибка (ах-ха, в ДНК, смешно, хахаха, хаха) или непонимание какого-то основного момента, но я не могу понять, какого именно.
Мне даже не ответ нужен, а просто вектор поиска. Чтобы я знал, где рыскать нужно, блин.

1. стандартный контроллер работает с одной записью... следовательно для такой "сложной страницы" надо контроллер кастомный. Логика его будет проста как помидор. Базовый компонент страницы будет таблица. Куда из контроллера ты будешь передавать список объектов.
2. запиленную кнопку Save вешай на метод в контроллере. Например:

public void save() {

Good__c good = new Good__c();
//fill object fields
insert good;
}

а в commandButton привяжи свойство rerender к id таблицы. И, да. по ререндеру таблицы значения списка объектов должно обновляться. То есть:
public List<Good__c> goodsList {get; set;}

.....
public void save() {
Good__c good = new Good__c();
//fill object fields
insert good;
goodsList = [SELECT Id, Name, ....., FROM Good__c WHERE ....];
}

либо
public List<Goog__c> getGoodsList() {

return [SELECT Id, Name, ....., FROM Good__c WHERE ....];

и все. страница даже рефрешиться не будет.
3. Отдаленно - паттерн MVC. Ближе - изучай кастомный контроллер. Стандартный удобно использовать например при редактировании одного объекта, или обрабатывать некоторую логику связанную с одним объектом (в таких случаях придется знакомиться с контроллерами-расширениями). В общем, задача и вправду несложная, но и стандартного контроллера для нее, увы, недостаточно. Да и кастомный выйдет строк в 30...

Вообще, главное уловить суть того или иного тега. Я, например, тоже не сразу понял, чем inputField отличается от inputText и т д

alex_flx, хотел бы я радостно ответить: "йо-хо-хо, теперь я всё понял". Но это будет всё же преждевременно! НЕ мог бы ты чуть-чуть подробнее расписать вот это "а в commandButton привяжи свойство rerender к id таблицы.",сам, так сказать, принцип. А то я что-то не могу, блин, найти эту фразу в коде никак.
Но в принципе не много я, кажется, начинаю понимать, почитаю MVC для чайников.
Спасибо!

вот есть у тебя таблица:

<apex:pageBlockTable value="{!products}" var="item" id="myTable">

<!-- Table Content-->
</apex:pageBlockTable>

где {!products} - ссылка на переменную в контроллере.

еще у тебя есть кнопка

<apex:commandButton action="{!save}" value="Save" rerender="myTable" />

где {!save} - ссылка на метод save() контроллера, где мы будем, например, сохранять новый объект, а rerender="myTable" есть перечисление элементов которые мы хотим перерисовать, то есть таблицу.

По нажатию на кнопку у тебя будет выполняться метод save() и перерисовываться таблица. Контент таблицы, напомню, лежит в переменной products контроллера. И чтобы новая запись попала в таблицу, надо ее добавить в список. Например:

public Product__c product { get; set; }

public List<Product__c> products { get; set; } // в конструкторе контроллера получаешь список продуктов запросом или как хочешь
............
public void save() {
product = new Product__c(); //создаем новый объект
.......//заполняем поля из инпутФилдов или других переменных
insert product; //инсертим в базу
products.add(product); // добавляем в лист
}

как то так...

Alexey Pchelkin, аааааа, кажется дошло чуть-чуть!!! Т.е. кое-что всё равно не складывается, но надеюсь, что уж до этого теперь и сам сумею дойти! Спасибо!!!

NOWICKED
Вроде всё просто - считай HTML только с другими тегами, но вот не получается никак.

Некоторые apex:теги действительно что-то вроде аналогов HTML, но в целом разметка и apex:теги - это разные сущности.

Начну издалека: десктопное приложение имеет свой код и граф интерфейс для взаимодействияс пользователем. Графический инетрфейс собирается из различным компонентов - кнопки, диалоги, списки, таблицы и прочее.

Давным давно, когда стало понятно, что приложения будут работать через http и их интерфес выводится (главным образом) в браузере, в .net перенесли идею графических компонентов в создание интернет-приложений, но там это asp:теги.

Так вот такой тэг является графически-функциональным компонентом (аналогично как в дескопном приложении), который определенным образом выглядит и делает определенные вещи (на сервере и в браузере). Все это настраивается через его атрибуты.

А как это все будет реализовано в браузере (через HTML CSS JS) - это уже разработчика не должно волновать - все сделат VF, плюс для разных браузером он может выдавать разную, более оптимизированную разметку.

Поэтому apex теги - это не аналог html, это готовые строительные блоки-секции (иногда и более мелкие кирпичи) для создания страниц, и их нужно изучать отдельно.

Den Brown
Поэтому apex теги - это не аналог html, это готовые строительные блоки-секции (иногда и более мелкие кирпичи) для создания страниц, и их нужно изучать отдельно.

Нет, я просто так выразился для простоты. То, что это разные вещи - понятно, а позавчера, когда один хороший человек помог мне решить проблему всего лишь подредактировав мою наивную страницу, это стало для меня совсем уж очевидным и наглядным подтверждением.

Господа, вот у меня новая проблема.Благодаря добрым людям у меня появилась и страница с контроллером и даже один метод. Сейчас же я нуждаюсь в методе, который бы удалял объект. Хочу я это сделать так: в таблице, где размещены мои объекты (записи о них, т.е.) в конце, после каждой записи в отдельном столбце вставить кнопку удаления и её уже привязать к методу deleterec(). В очередной раз нуждаюсь в вашей помощи, что должно быть в этом методе?

public void deleterec(){
Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c];
delete sht;
}
И я получаю закономерный ответ: System.QueryException: List has more than 1 row for assignment to SObject
Чего тут не хватает? Ну нет в воркбуках просто общих примеров/образцов:(

System.QueryException: List has more than 1 row for assignment to SObject

Это потому что Вам как результат возвращается целый список объектов.

Sam__c[] sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c]; 

List<Sam__c> sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c];
Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c LIMIT 1];

// мне очень нравиться вариант:
Map<Id, SObject> mapRecords = new Map<Id, SObject>[SELECT Id, ...];
Id[] recordsId = mapRecords.keySet();
SObject[] records = mapRecords.values();

Если Вам нужно удалять запись по нажатии на кнопку delete сначала нужно определить какую запись нужно удалять. Например можно узнавать по ид, какой рекорд удалять и тогда ваш метод удаления будет таковым:

public void deleterec(){ 

Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectedRecordId];
try {
delete sht;
} catch (System.DMLException ex) {
// TODO: add handle exception
}
}

Чтобы узнать ид рекорда для этого можно, например если Вы используете apex:comandButton, использовать еще apex:param

Например:

<apex:form>

<apex:pageBlockTable value="{!records}" var="item">
<apex:commandButton value="Delete Record" Action="{!deleterec}">
<apex:param name="recordId" assignTo="{!selectedRecordId}" value="{!item.Id}" />
</apex:commandButton>
</apex:pageBlockTable>
</apex:form>

Или можно создать на VisualForce странице actionFunction apex:actionFunction, и использовать ее как javascript метод. Например, почепить на onclick = "deleteRecord('{!item.Id}')":

<!--VISUALFORCE-->

<apex:form>
<apex:actionFunction action="{!deleterec}" name="deleteRecord" rerender="tableOfRecords">
<apex:param name="recordId" assignTo="{!selectedRecordId}" value="" />
</apex:actionFunction>
</apex:form>

У контролере нужно создать property, поле в которое будет сохраняться ид рекорда:

/**Contrloller*/

public String selectRecordId {get; set;}

P.S. Я постоянно стараюсь не использовать SOQL, DML там где можно обойтись без них, не раз приходилось бороться из salesforce лимитами. Поэтому мне кажется, лучше бы рекорди хранить например в мапе Map<Id, SObject>(), и тогда в случае удаления можно просто с мапы вытянуть нужный объект без использования soql запроса.

Alex Tsitsura
Ого, спасибо, сейчас буду разбираться с этими, для меня это всё сложно очень пока!
Но всё же, я страшно извиняюсь, нет ли варианта без мар? Т.е. именно со списком? Или, хотя бы можно создать мар чисто в методе, чисто для удаления? А то у меня чего-то не получается создать список ID, хотя в девелопергайде такой пример есть и не ругается никто.

Если Вы хотите использовать List<SObject>, то перед удалением нужно узнать индекс елемента который Вы хотите удалить, для етого нужно пробежатся по всему списку.

// Contact[] contacts = Ваш список обектов

for (Integer i=0; i<contacts.size(); i++) {
Contact c = contacts[i];
if (/**условие, например selectedId = c.Id*/) {
delete contacts.remove(i);
break;
}
}

Alex Tsitsura
/**условие, например selectedId = c.Id*/

Вот именно это я и не понимаю! Какое условие должно быть, если я через кнопку или там командлинк хочу удалять?
И всё же насчёт мар: можно создавать эту самую мар тупо в методе? А то мне способ очень понравился, но во с selectedRecordId что-то всё не получается.

Мапу можно создать например в конструкторе, ну или там где Вы создаете список обектов. А по поводу условия, на странице у Вас есть допустим apex:commandButton с apex:param, такой как я писал выше, Вы через param можете передать значение в переменую контролера, ето может быть например ид обекта. Тоесть, когда Вы нажали на кнопку delete, сразу param передается в контролер, и уже в методе delete Вы можете его использовать.

Это я не правильно выразился снова. Имел в виду, что всё компилируется-то замечательно, но в ответ:
System.QueryException: List has no rows for assignment to SObject.

Вот, собственно мой контроллер:

public class XXXZZZ {

public String selectRecordId {get; set;}
Map<Id, Sam__c> mapRecords = new Map<Id, Sam__c>();
Set<ID> recordsId = mapRecords.keySet();
Sam__c[] records = mapRecords.values();
//Всякая фигня, которая не относится к делу
public void deleterec(){
Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectRecordId];
delete sht;
}

Вот фрагмент страницы:

<apex:page Controller="XXXZZZ">

<apex:form >
<apex:pageBlock title="Products">
<apex:pageBlockSection columns="1">
<apex:pageBlockTable value="{!products}" var="samz" id="myTable">
// тут три колонки

//колонка с кнопками удаления напротив каждой записи
<apex:column headerValue="Del">
<apex:commandButton value="Delete Record" Action="{!deleterec}">
<apex:param name="recordId" assignTo="{!selectedRecordId}" value="{!samz.Id}" />
</apex:commandButton>
</apex:column>
</apex:pageBlockTable>

<apex:inputField value="{!newRowObj.Name}" label="Name"/>
<apex:inputField value="{!newRowObj.Description__c}" label="Description"/>
<apex:inputField value="{!newRowObj.Price__c}" label="Price"/>

</apex:pageBlockSection>
<apex:pageBlockButtons >
<apex:commandButton action="{!save}" value="Save"/>
</apex:pageBlockButtons>
</apex:pageBlock>
</apex:form>
</apex:page>

Ну где моя ошибка, мудрейшие?

думается, в selectedRecordId закрался NULL... дебажить надо, Штатные ясновидцы в отпуске.

Вот эта конструкция очень опасная:

Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectRecordId];

если у тебя в selectRecordId будет невалидный ID или NULL, то получишь ошибку, которую ты получаешь.

Sebastian Pareyro
System.QueryException: List has no rows for assignment to SObject

Нужно всегда получать результат в List (в твоем случае List<Sam__c>) и проверять его на .size() == 1, что означает что запись действительно существует и ее можно дальше обрабатывать. И не будет тогда валится глобальный ексепшен. Это кстати важно если захочешь пройти Security code review в AppExchange, там такие конструкции не пропустят (если мне память не изменяет).

А твою ошибку отловить очень просто - поставь в коде перед твоим SOQL - SYSTEM.DEBUG(selectRecordId). Увидишь что тебе приходит в твой запрос, который и пытается вернуть пустой List.

Sebastian Pareyro, вот набросал пример страницы, можеш попробовать у себя создать страницу и поэкспериментировать.

<apex:page controller="DeleteRecordController">

<apex:form >
<apex:pageBlock title="Example">
<apex:pageMessages id="messageBlock"/>
<apex:pageBlockTable value="{!records}" var="item" id="dataTable">
<apex:column value="{!records[item].Name}" />
<apex:column headerValue="Action">
<apex:commandLink value="Delete" action="{!DeleteRecord}" reRender="dataTable, messageBlock">
<apex:param name="recordId" value="{!item}" assignTo="{!selectRecordId}" />
</apex:commandLink>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>

и котроллера:

public with sharing class DeleteRecordController {

public Map<Id, Contact> records {get; private set;}
public String selectRecordId {get; set;}

public DeleteRecordController() {
records = new Map<Id, Contact>([SELECT Id, Name FROM Contact LIMIT 20]);
}

public PageReference DeleteRecord() {
try {
Contact item = records.get(selectRecordId);
delete item;
records.remove(selectRecordId);
} catch (Exception ex) {
// handled exception
ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.ERROR, ex.getMessage());
ApexPages.addMessage(myMsg);
}
return null;
}
}

Alex Tsitsura
Воу! Круто! Спасибо! Сейчас буду переделывать и разбираться! Хоть ApexPage'ами и PageReference'ами разберусь нормально!

Dmitry Shnyrev
Я не совсем понял, т.е. вместо

public void deleterec(){ 

Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectRecordId];
delete sht;
}

нужно
public void deleterec(){ 

List<Sam__c> sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectRecordId];
delete sht;
}

?

И вообще, друзья, это задание мне год назад прислали в качестве тестового. Так вот, у меня возникают сомнения: раз всё так сложно и неоднозначно, то может я просто рою не в том направлении? Или это нормально для тестового задания?

public void deleterec(){ 

Sam__c sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectRecordId];
delete sht;
}

В этом коде может быть ситуация когда, Вы хотите получить объект по selectRecordId, а selectRecordId оказался null, или каким то бредом, или даже может быть что в базе уже нет объекта с таким ид.

Для этого можно можно сначала результат занести в список, и потом проверить что размер списка например 1.

public void deleterec(){ 

Sam__c[] sht = [SELECT Id, Name, Description__c, Price__c FROM Sam__c WHERE Id = :selectRecordId];
if (1 == sht.size()) {
delete sht[0];
} else {
// record not exist
}
}

P.S. Здесь нет ничего сложного, просто как говорится нужно набить руку. Я сам только недавно начал работать с salesforce, и практически каждый день сталкиваюсь с проблемами.

О! Наконец-то понял, что имелось в виду с этой проверкой размера! Благодарю!
Не работает, правда, ошибку не выдаёт, но тупо перезагружает страницу, ничего не удаляя.
Нифига себе "недавно начал", мне бы так начать, блин. А я только на форумах выцыганиваю помощь.

Вот здесь:

if (1 == sht.size()) {

delete sht[0];
}

не нужно делать sht[0] (хотя это не будет ошибкой). Нужно просто сделать delete sht.
Delete, как любая DML операция работает как к объектом, так и со списком объектов.

По поводу самой ситуации - значение данной ошибки слишком преувеличено. Поверь, будут встречаться ошибки страшнее и запутаннее. Учись "дебажить" (отлаживать) код в голове. Ты должен знать где и какая переменная что должна содержать и что любая из команд должна вернуть.

Самый простой алгоритм выявить ошибку - узнать в какой строчке ошибка и прямо перед ней вставить вывод всех приходящих переменных в лог (на экран). Обычно 90% ошибок в том что у тебя на входе будет не то что ты ждешь (ждет твой код). Остальные 10% - вставляешь текст ошибки в google и точно найдешь пример решение проблемы.

Наш форум тоже супер! Пиши, не стесняйся, но скорость решения твоей проблемы увеличится в разы

Sebastian Pareyro
Не работает, правда, ошибку не выдаёт, но тупо перезагружает страницу, ничего не удаляя.

Потому что ты наверное удаляешь List который тебе вернул SOQL, а он пустой.

Dmitry Shnyrev
Ааа, ты это имел в виду. Сейчас попробую и так сделать.
Понимаешь, когда ты говоришь мне "научись дебажить", то это мне не кажется очевидным как тебе и другим местных обитателям. Про debug я, понятное дело, знаю, но так ловко его где-то применять не представляю как. Мне бы пока с проклятыми методами разобраться.
Спору нет - форум очень клёвый, спасибо, что не поленился его сделать. Я бы офигел выпрашивать помощи у людей в вк. Вон, Алексей Пчёлкин меня слегка уже недолюбливает:)

Sebastian Pareyro
Ааа, ты это имел в виду. Сейчас попробую и так сделать.
Понимаешь, когда ты говоришь мне "научись дебажить", то это мне не кажется очевидным как тебе

А тут нет ничего неочевидного. Любым удобным способом выводишь переменную хоть в лог, хоть на экран, хоть на мыло себе посылаешь (зависит от ситуации). Видишь что в переменной нет нужного значения, идешь выше по коду и смотришь где данная переменная заполняется - так сказать разматываешь клубок пока не найдешь где у тебя ошибка в коде. Так делают везде, хоть на Salesforce, хоть на PHP, хоть на javascript.

Sebastian Pareyro
Про debug я, понятное дело, знаю, но так ловко его где-то применять не представляю как.

Ничего тут сложного нет - записал переменную в лог, открыл лог и смотришь что в переменной. Вот одна моя старая статья на эту тему Salesforce debugger - отладка кода

Sebastian Pareyro
Вон, Алексей Пчёлкин меня слегка уже недолюбливает:)

Ну это я думаю ты сам себе навыдумывал - Salesforce разработчики очень добрые люби :)

Еще, иногда для дебага можно использовать View State, очень удобная вещь.

Dmitry Shnyrev

Вот посмотри, такое мне выдаёт лог:

06:53:23.050 (50647521)|VARIABLE_SCOPE_BEGIN|[21]|sht|LIST<sam__c>|true|false
06:53:23.053 (53524478)|VARIABLE_ASSIGNMENT|[21]|sht|{"serId":1,"value":[]}|0x10bbf949

Но после прочтения твоей статьи и соответствующей главы в salesforce_pages_developers_guide, всё, что я вижу в логе - это время, начало/окончание выполнения, номер строки и т.д. А понять
это "serId":1,"value":[]}|0x10bbf949" не могу. Что это значит?

Sebastian Pareyro,
Попробуйте в коде использовать System.debug();

Например, функция deleteRecord:

public void deleterec(){ 

System.debug('********* select record Id: ' + selectRecordId);
Sam__c[] sht = [SELECT Id, Name, Description__c, Price__c
FROM Sam__c WHERE Id = :selectRecordId];
// '*********' я использую звездочки для быстрого поиска
// нужного дебага(как в дебаг логе, так и в коде)
System.debug('********* list: ' + sht);
if (1 == sht.size()) {
delete sht;
} else {
// record not exist
}
}

Alex Tsitsura

Да, спасибо, нашёл:

USER_DEBUG|[21]|DEBUG|********* select record Id: null

То, о чём все меня предупреждали. Да что за скотство?
Причем я переделал свои страницу и контроллер строго по образцу твоей страницы, всё равно в PageMessage мне сообщают, что Error:Attempt to de-reference a null object.
Может я как-то неправильно объект свой создал с самого начала? Может есть какая-то обязательная галочка, которую я упустил? Ну, блин!

сейчас надо разобраться почему Id объекта не попадает в переменную selectedRecordId или как там она у тебя называется...
Внимательно пересмотри контроллер и страницу. Посмотри на имена переменных.. Думается, что где то что то не так =)

Парни, я снова за помощью.
Делаю метод для поиска. По слову ЦЕЛИКОМ - без проблем вроде. Но как сделать по части слова?
Вот часть моего контроллера, подскажите, пожалуйста где я туплю:

public void setSearchText(String s) {

searchText = '*'+s+'*';
}

public PageReference doSearch() {
results = (List<Sam__c>)[FIND :searchText RETURNING Sam__c(Name, Description__c, Price__c)][0];
return null;
}

Один хороший человек посоветовал мне поменять '*'+s+'*' на '%'+s+'%', но не сработало.

Sebastian Pareyro
public PageReference doSearch() {
results = (List<Sam__c>)[FIND :searchText RETURNING Sam__c(Name, Description__c, Price__c)][0];
return null;
}

признаться, никогда FIND не использую, ибо есть старый добрый LIKE
ПС:для лайк - проценты работают, для файнд - работает только * и ?, но не в начале строки

Кстати да, FIND - этот SOSL (не путать с SOQL)
Очень коварная штука. Особо не стоит ей пренебрегать.
Я советую использовать только в случае если надо искать по long text area или rich text area (длинные текстовые поля!) в которых искать с помощью LIKE нельзя.
Во всех остальных случаях, правильно заметил Максим, используем SOQL т.е. получится

WHERE name LIKE "%частьслова%"

По поводу коварности SOSL - очень часто слышал от разработчиков и сам иногда сталкивался -
бывает запрос выдает неадекватные результаты по незявисящим от вас причинам.

Ага, благодарю вас, господа, будет лайк! Может и подскажите заодно и синтаксис запроса не только по имени, но и по другим полям? Что-то я не найду никак, нет ни одного примера в гайдах и воркбуках. Или это невозможно и нужно делать IF?

Все просто

комбинируешь любое количество полей через OR (или AND)

String criteria = '%someword%';
... WHERE name LIKE :criteria OR address LIKE :creteria OR ...

ну в общем как-то так, а дальше как душе угодно комбинируй.

Всё отлично! Я уже вижу! Спасибо!

А чтобы искать по дате нужно использовать valueOf() или DATEVALUE(expression)? Или я что-то не то себе думаю.

Чтобы искать по дате у тебя должна быть переменная c типом Date или Datetime.
Как ты ее получишь - есть много способов: парсить из строки или делать new Date() смотри по ситуации.
А потом просто в SOQL запросе ставишь WHERE birthdate > :myDate

ps. DATEVALUE(expression) здесь не подойдет - это для FORMULA, а тебе нужно для Apex Code

Товарищи! Моя полуторамесячная эпопея с созданием аж целой страницы, контроллера, триггера и правила проверки подходит к концу, у меня осталось два вопроса:

1. Как сделать, чтобы инпутфилды и инпуттексты очищались после поиска/сохранения и т.д? Я запихивал их в блоксэксшн и обновлял ререндером, но что-то не срабатывает мой хитрый план.

2. Как протестировать это:

public PageReference DeleteRecord() { 

try {
/*Sam__c samz = records.get(selectRecordId);
delete samz;
records.remove(selectRecordId); */
for (Integer i=0; i<records.size(); i++) {
if (selectRecordId == records[i].Id) {
delete records[i];
records.remove(i);
break;
}
}
} catch (Exception ex) {
// handled exception
ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.ERROR, ex.getMessage());
ApexPages.addMessage(myMsg);
}
return null;
}

Т.е. как добраться до этого:
for (Integer i=0; i<records.size(); i++) {
if (selectRecordId == records[i].Id) {

и этого

catch (Exception ex)

Сил моих нет. Все перепробовал, а в итоге мне до75% покрытия не хватает 9% из-за этой фигни.
Спасибо.

Sebastian Pareyro
1. Как сделать, чтобы инпутфилды и инпуттексты очищались после поиска/сохранения и т.д? Я запихивал их в блоксэксшн и обновлял ререндером, но что-то не срабатывает мой хитрый план.

Вот один из способов который мне понравился

<apex:form>

<apex:inputtext value={!Value}/>
<apex:commandButton value="Clear" onclick="this.form.reset();return false;" />
</apex:form>

А так-то можно написать метод, который вызывать при нажатии на кнопку, который будет просто в поля записывать null.

Sebastian Pareyro
2. Как протестировать это:

Нужно в тесте указать какой рекорд ты хочешь удалить, то есть дать значение переменой "selectRecordId", а потом уже запуска метод PageReference DeleteRecord().

<apex:form>

<apex:inputtext value={!Value}/>
<apex:commandButton value="Clear" onclick="this.form.reset();return false;" />
</apex:form>

Ну, он чистит инпуты, но только чистит:( Сам поиск не выполняется.

Короче, сделал через контроллер. Вариант варварский, скорее всего, но работает, а о большем я пока и мечтать не могу.

Sebastian Pareyro
1. Как сделать, чтобы инпутфилды и инпуттексты очищались после поиска/сохранения и т.д? Я запихивал их в блоксэксшн и обновлял ререндером, но что-то не срабатывает мой хитрый план.

Просто в методе очищай значения переменных, которые выводятся в блоке rerender.
инпутфилды и инпуттексты - это всего лишь вывод содержимого public переменных.

Sebastian Pareyro
ак протестировать это:

во-первых эти две строчки можно объединить в одну - тогда процент непокрытого кода в exception блоке уменьшится
ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.ERROR, ex.getMessage());
ApexPages.addMessage(myMsg);

во-вторых в чем проблема добраться до этого
for (Integer i=0; i<records.size(); i++) {
if (selectRecordId == records[i].Id) {

смотри что у тебя в переменных, если чего не хватает создавай в тест методе и передавай в метод.

Мне уже трижды отвечают именно так, но я не понимаю, хнык-хнык.

static testMethod void deletetest {
qwer qw = new qwer();//это контроллер
list <sam__c> records = new List <sam__c>();// собственно объекы на стрнице
Sam__c Z = new sam__c(Name = 'll', Description__c = 'desll', Price__c = 10.00);
insert Z;
records.add(Z);
//а здесь я уже и так, и эдак пытаюсь добраться до "if (selectRecordId == records[i].Id)"
string selectRecordId = Z.Id;
integer i=0;
qw.DeleteRecord();
}

Ну не получается покрыть if (selectRecordId == records[i].Id) и далее!

А где в твоем тест методе передача переменных в сам контроллер (инициализация переменных контроллера)
перед вызовом метода DeleteRecord()

Все твои records, selectRecordId, i переменные видны в тест методе, но контроллер их не видит (области видимости переменных)

надо сделать

qw.records = records;
...

Это заполнит переменные внутри контроллера, а замет можно вызывать метод, которые их и будет использовать.

Это ж ООП. Как пишут в соседней ветке.

Благодаря Alex Tsitsura с этим покончено.

Dmitry Shnyrev
qw.records = records;

Но вот это бы хотелось понять. Не очень пока получается. Если можно, чуть-чуть более развёрнутно, чтобы я ухватил эту мысль!

Sebastian Pareyro
qwer qw = new qwer();//это контроллер

в этой строчке ты создал экземпляр контроллера. У тебя отработал конструктор.
Все переменные (свойства) контроллера, которые инициализируются в конструкторе будут заполнены.
Можно проверить если обратиться в тест методе qw.some_var (some_var должна быть public, иначе получишь ошибку)
Теперь фишка. Обычно в реальной работе контроллера после его инициализации строится страница и показывается тебе. Ты заполняешь нужные поля нажимаешь кнопку и отправляешь данные в контроллер, вызываешь метод (в твоем случае DeleteRecord).
Так вот в тест методе нет страниц и ты ничего не нажимаешь. Вот это и надо воспроизвести путем ручного изменения переменных контроллера, как будто отработала страница.
qw.some_var = some_value
после этого вызывается сам метод, который и будет использовать переменные. Тут никакой магии нет!

Ааааа, ясно, понял. Спасибо.

Interesting information? Help us, post link to social media..