Расширьте объект или добавьте ko.computed, чтобы вернуть дополнительное свойство в объекте, которого нет в базе данных.

Никак не могу сообразить, как это сделать. Я начал с решения Джули Лерман «BreezyDevices», чтобы учиться, и я использовал ее модель представления javascript в качестве основы.

У меня есть:

//properties and methods to expose via this class
var vm = {
    game: ko.observableArray([]),
    save: function () {
        dataservice.saveChanges();
    },
    reset: function () { dataservice.reset(getAllGames) },

};

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

На моей html-странице я хочу связать конкретные свойства базы данных, возвращаемые как часть объекта «игра», но я также хочу создать свойство «результат» для каждой игры, которое вычисляется на основе функции javascript, которая перебирает каждый заданный счет и возвращает значения соответственно.

Я попытался использовать макет в решении бриза «Todo» и сразу же настроил его в приведенном выше коде:

initVm();

function initVm() {
    addComputeds();
}

function addComputeds() {
    vm.result = ko.computed(function () {
        var ourSets = getResult().ourSets;
        var theirSets = getResult().theirSets;

        if (ourSets == 0 && theirSets == 0) {
            return "No Result";
        }
        return (ourSets > theirSets ? "Won " : "Lost ") + "<b>" + ourSets.toString + "</b>-" + theirSets.ToString;
    });
}


function getResult() {
    var ourSets = 0;
    var theirSets = 0;

    vm.game().forEach(function (game) {
        for (var gs in game.Sets) {
            if (gs.ourScore > gs.theirScore) {
                ourSets +=1;
            }
            else {
                theirSets +=1;
            }               
        }
    });

    return {
        ourSets: ourSets,
        theirSets: theirSets
    };
}

но мне кажется, что это добавит «результат» к модели представления (vm), а не к каждому игровому объекту? Кроме того, когда я запускаю код, он не выдает ошибку, но он не создает свойство «результат» везде, где я могу видеть, и просто не работает.

Глядя на это снова, когда я добавляю его сюда, я вижу, что это неправильно, поскольку для обработки каждого результата необходимо иметь дело с каждым конкретным игровым объектом, а не с массивом игр (поэтому мне нужно что-то в vm.games.result, а не vm.result), но я слишком новичок в этом, чтобы понять, как работать с каждым отдельным игровым объектом. Мой кодирующий мозг .net заставит меня передать каждую игровую сущность в цикле функции, чтобы вернуть результат для этой игры, но я не знаю, как это работает с бризом/нокаутом.

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


@BeaverProj

У меня есть файл main.js, в котором это происходит:

(function (root) {
    var app = root.app;

    app.logger.info('Please wait... data loading');

    ko.applyBindings(app.gameViewModel, $("content").get(0));

    $(".view").css({ display: 'block' });
}(window));

Теперь отредактировали верхний раздел следующим образом:

var vm = {
    game: ko.observableArray([]),
    save: function () {
        dataservice.saveChanges();
    },
    reset: function () { dataservice.reset(getAllGames) },
    result: ko.computed(function () {
        var gameRes = getResult();
        var ourSets = gameRes.ourSets;
        var theirSets = gameRes.theirSets;

        if (ourSets == 0 && theirSets == 0) {
            return "No Result";
        }
        return (ourSets > theirSets ? "Won " : "Lost ") + "<b>" + ourSets + "</b>-" + theirSets;
        })
};

"getResult" теперь ссылается на "app.gameViewModel.game().forEach(function (Game) {" вместо "vm..."

Как и раньше - ошибок нет, но и результатов тоже. Я все еще получаю массив «игры», но больше ничего. Приведенная выше модель просмотра по-прежнему кажется мне неправильной... "Результат" должен быть привязан к игровому объекту (vm.game), а не к виртуальной машине - в настоящее время это дает vm.result, и есть результат для каждой игры (поэтому vm. game.result), а не для массива игр. Вот почему мне интересно, нужно ли мне расширять сущность с помощью бриза. Я мог бы сделать это в обычном javascript, но кажется, что бриз или нокаут должны сделать это намного проще?


person TheMook    schedule 20.01.2013    source источник


Ответы (4)


Несколько вещей:

  • Я вполне уверен, что вы захотите, чтобы ваша вычисляемая функция была объявлена ​​внутри определения модели представления. Просто поместите его под функцию сброса.

  • В коде, которым вы поделились, вы не инициализировали свою модель представления и не поделились ею с Knockout.js. Вам нужен вызов где-то так:

    var gameViewModel = новый vm(); ko.applyBindings(gameViewModel);

  • После этого изменения в вашем методе getResult() вы должны ссылаться на gameViewModel (экземпляр) вместо переменной vm (определение класса).

  • И последнее, что не должно влиять на то, работает ли он слишком много, заключается в том, что вы должны сделать только один вызов getResult() в вашем вычисленном результате и присвоить возвращаемое значение переменной. В противном случае вы вычисляете его дважды, что является пустой тратой ресурсов и технически может меняться между вызовами.

person BeaverProj    schedule 20.01.2013
comment
Где вы создаете экземпляр app.gameViewModel с новой виртуальной машиной? - person BeaverProj; 21.01.2013
comment
Прямо внизу файла vm js: dataservice.getAllGames(vm.game); app.gameViewModel = вм; Этот бит работает нормально - я без проблем возвращаю свойства базы данных, это вычисляемое свойство, которое я не получаю. - person TheMook; 21.01.2013
comment
Я предполагаю, что вы имеете в виду новый vm ()? - person BeaverProj; 21.01.2013
comment
Что вы получите, когда введете это в консоль: ko.toJSON(app.gameViewModel)? - person BeaverProj; 21.01.2013
comment
Нет, я имею в виду вм. Как я уже сказал, я использую BreezyDevices Джулии Лерман (и Уорда Белла) в качестве базы для обучения: msdn.microsoft.com/en-us/magazine/jj863129.aspx и это то, что у них есть. Я полагаю, вы имели в виду консоль Google? Если это так, я получаю: ko.toJSON(app.gameViewModel) TypeError: Преобразование круговой структуры в JSON - person TheMook; 21.01.2013
comment
Я очень сомневаюсь в этом, так как моих навыков недостаточно, чтобы подделать данные базы данных! Если вы не знаете о существующей скрипке, которая делает что-то подобное, что я могу адаптировать? Сегодня днем ​​я добился некоторого прогресса, используя расширение сущности бриза. Как раз собираюсь попробовать и посмотреть, смогу ли я сейчас решить, как интегрировать метод вместо статического свойства. Обновлю позже. - person TheMook; 22.01.2013

Итак, после долгих возни я добился некоторого прогресса. Извините, Уорд (если вы это читаете) — ваша документация может быть полезной для опытных программистов, но не для нубов!)

Вершина dataservice.js теперь такова:

var manager = new breeze.EntityManager(serviceName);
var store = manager.metadataStore;

var Game = function () {
    //this.result = "Won 3-2";

};

var gameInitializer = function (game) {
    //game.result = "Won 3-2";
    game.result = function () {
        return getResult(game);
    }();
 };

store.registerEntityTypeCtor("Game", Game, gameInitializer);

и функция getResult:

function getResult(game) {
    var ourSets = 0;
    var theirSets = 0;

    game.Sets().forEach(function (gs) {
        if (gs.ourScore() > gs.theirScore()) {
            ourSets += 1;
        }
        else {
            theirSets += 1;
        }
    }
    );
    if (ourSets == 0 && theirSets == 0) {
                return "No Result";
    }
    else {
            return (ourSets > theirSets ? "Won " : "Lost ") + ourSets + "-" + theirSets;
    }
}

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

Чего я до сих пор не знаю, так это того, является ли это самым (или даже единственным) способом сделать это, или я мог бы добиться аналогичного результата с помощью нокаута или даже стандартного javascript. Выполнение этого в бризе, по-видимому, обрабатывает передачу игрового объекта, который необходим для последующего раскрытия связанного объекта «Наборы». Просто нужно было быть осторожным, чтобы ссылаться на свойства с добавленными скобками, иначе все потерпело неудачу.

Однако мне не нравится идея загромождать службу данных конструкторами, инициализаторами и функциями, специфичными для модели. На мой взгляд, большая часть того, что я сделал, относится к файлу «vm.game.js», а не к общему файлу «dataservice.js», как сейчас.

Теперь я собираюсь попытаться изменить код. Мне может понадобиться некоторое время, чтобы разобраться со ссылками!

person TheMook    schedule 21.01.2013

ваш инициализатор после построения очень похож на ответ на эту проблему, который заключался в использовании плагина ko mapping doc'd @ http://knockoutjs.com/documentation/plugins-mapping.html.

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

var mapping = {
  game : {
    create : function(options) {
            var game = options.data;
            game.results = ko.computed( function(){ 
              //your result sets calc here
            }  );

            return game;
        }
  }
}

еще лучше было бы определить свой собственный тип GameVM и просто поместить его туда.

function GameVM( GameData )
{
    var self = this;
    this.Sets = ko.mapping.fromJS( GameData.Sets );
    this.Results = ko.computed( function(){
         self.Sets()...
    } );

}

и отображение

 var mapping = {
      game : {
        create : function(options) {
               return new GameVM( options.data );
      }
    }

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

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

person Chris DaMour    schedule 22.01.2013
comment
Зачем вообще использовать KO-мэппинг? Я понимаю стремление вывести эту логику из сущности и поместить ее в собственную виртуальную машину. Но и тогда я не вижу смысла прибегать к ко-мэппингу. Ко-отображение — это метод обертывания объектов, у которых отсутствуют наблюдаемые. Но эти объекты Breeze являются наблюдаемыми. - person Ward; 22.01.2013
comment
И спасибо, что предупредили нас о возможностях карты KO. Это помогает узнать об альтернативах и критериях выбора одного по сравнению с другим. Все хорошо. - person Ward; 22.01.2013

Не парься. Продолжайте исследовать. Мы все учимся.

Я не знаю, лучше ли поместить это в ViewModel (VM) или в сущность. Нет внутреннего правильного ответа.

Предположим, вы хотите, чтобы это было в сущности (потому что это более интересно с точки зрения Breeze). Следующий вопрос: вам нужно, чтобы он был наблюдаемым?

Вы написали: «это простой автономный результат вычисления, который используется только для отображения. Ничто не может измениться на странице, что повлияет на результат, это уже произошло». Это говорит о том, что в этом нет необходимости. наблюдаемый, поэтому вам не понадобится вычислять нокаут. Ваше самое последнее решение даже не представляет его как свойство; Я предполагаю, что что-то в вашей виртуальной машине вызывает getResult().

Это наводит меня на мысль, что вы можете предпочесть зарегистрировать пользовательский конструктор и поместить getResult в прототип этого конструктора:

var Game = function () { }

Game.prototype.getResult = function () {
  var ours = 0;
  var theirs = 0;
  this.sets().forEach(function (s) {
    (s.ourScore() > s.theirScore()) ? ours += 1 : theirs += 1;
  });
  return (ours || theirs) ? 
      (ours > theirs ? "Won " : "Lost ") + ours + "-" + theirs :
      "No result";
}

store.registerEntityTypeCtor("Game", Game); // ctor but no initializer

С другой стороны, если вы считаете, что результаты могут измениться, и хотите, чтобы экран обновлялся так же, как и они, вы, вероятно, захотите переместить логику в вычисляемый results нокаут. Наблюдаемые ourScore и theirScore должны поддерживать вычисление results в актуальном состоянии. Я вот думаю вслух, не пробовал.

Вы бы определили, что results вычисляется в инициализаторе, а не в ctor. Почему? Потому что наблюдаемые KO должны быть прикреплены к экземплярам, ​​а не к прототипу. Если вы определили его в ctor, Breeze может попытаться его сериализовать и отследить изменения. Лучше привязать его к сущности в инициализаторе; Breeze игнорирует элементы сущности, добавленные инициализатором.

При дальнейшем размышлении, возможно, я бы оставил метод getResult там, где он находится в прототипе, и написал инициализатор, который добавил бы вычисляемый ko... вот так (предупреждение: не проверено):

function gameInitializer(game) {
   game.results = ko.computed(function() { return game.getResults();});
}

store.registerEntityTypeCtor("Game", Game, gameInitializer);

А теперь серьезно: должна ли эта логика быть в службе данных?

Я не хотел бы иметь его в своем сервисе данных именно по той причине, по которой вы сами это называете. Этот тип логики выражает проблемы, присущие модели, и не имеет ничего общего с управлением доступом к данным. Можно смешать все вместе в dataservice в демоверсии. В «реальном» коде его можно было бы выделить в компонент model.js (я думаю о Game как о компоненте Model, а не компоненте ViewModel) и загрузить этот скрипт между dataservice.js и моделью представления. Теги скрипта .js.

person Ward    schedule 22.01.2013
comment
Спасибо за вклад, Уорд, очень признателен. Я могу понять более эффективный код javascript, используемый в функции getResult, и я буду использовать этот синтаксис в будущем. Однако я не понимаю преимущества включения функции getResult в прототип. Мне все еще нужно иметь инициализатор и определить свойство game.result, не так ли? В вашем фрагменте кода иначе нет game.result. Если я все равно это сделаю, в чем польза от наличия функции в прототипе? Это просто для того, чтобы мне не приходилось явно передавать игровой объект в функцию getResult? - person TheMook; 22.01.2013
comment
Размещение громоздкой логики в прототипе должно уменьшить объем памяти... по крайней мере, в принципе. Функция прототипа является общей для всех экземпляров Game, тогда как, если вы добавите ее в ctor, у каждого экземпляра будет своя собственная копия. Современные JS-движки могут оптимизировать это... я не знаю... но именно поэтому мы перемещаем код в прототип, когда можем. Не увлекайтесь синтаксисом, который я использовал. Это может быть не более эффективно. Мне просто легче видеть концепции, когда меньше синтаксического шума (мое предпочтение стиля). - person Ward; 22.01.2013
comment
Извиняюсь! Только что увидел, что вы ответили на это выше. - person TheMook; 22.01.2013
comment
Хм. Проблема, с которой я столкнулся, пытаясь выделить приведенный выше код для конкретной игры, заключается в том, что при загрузке игровой модели ПОСЛЕ службы данных модуль службы данных еще не знает об игровой модели! Сейчас это выглядит как классическая ситуация с курицей и яйцом. Я, конечно, предполагаю, что должен оставить конструктор игры и логику инициализации (и строку store.registerEntityTypeCtor) в службе данных? Или это тоже должно быть в игровой модели? Я могу ошибаться, но я думал, что не могу ссылаться на игровую модель, пока она не загружена, а она загружается после службы данных. - person TheMook; 22.01.2013
comment
Ржу не могу. Я знаю проблему. Модель нуждается в службе данных. Службе данных не нужно знать модель, хотя может потребоваться знать имена объектов модели. Взгляните на пример NoDb, который, как мне показалось, использует эту службу данных: Последовательность модель-VM (datacontext == dataservice). - person Ward; 23.01.2013