Skip to content

Commit f714f5f

Browse files
author
Alexej Yaroshevich
committed
v4.0.0 fixes
- Added global matcher support: `beforeEach` and `afterEach` (bem/bh#121). - Added `ctx.process` method (bem/bh#135). - Add `i-bem` class to element with `js` (bem/bh#122). - No-base modifier classes supported (bem/bh#132). - `processBemJson` now return standart BEMJSON (bem/bh#96). Changelog bem/bh@ee446ea
1 parent 72d9e08 commit f714f5f

16 files changed

+498
-168
lines changed

src/BH.php

+130-78
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,25 @@ class BH {
2929
* Неймспейс для библиотек. Сюда можно писать различный функционал для дальнейшего использования в шаблонах.
3030
*
3131
* ```javascript
32-
* bh.lib.objects = bh.lib.objects || [];
33-
* bh.lib.objects.inverse = bh.lib.objects.inverse || function(obj) { ... };
32+
* $bh->lib->i18n = BEM.I18N;
33+
* $bh->lib->objects = bh.lib.objects || [];
34+
* $bh->lib->objects.inverse = bh.lib.objects.inverse || function(obj) { ... };
3435
* ```
35-
* @var array
36+
* @var ArrayObject
3637
*/
37-
protected $lib = [];
38+
public $lib;
3839

3940
/**
4041
* Опции BH. Задаются через setOptions.
4142
* @var array
4243
*/
4344
protected $_options = [];
45+
4446
protected $_optJsAttrName = 'onclick';
4547
protected $_optJsAttrIsJs = true;
48+
protected $_optJsCls = 'i-bem';
4649
protected $_optEscapeContent = false;
50+
protected $_optNobaseMods = false;
4751

4852
/**
4953
* Флаг, включающий автоматическую систему поиска зацикливаний. Следует использовать в development-режиме,
@@ -80,6 +84,7 @@ class BH {
8084
* @constructor
8185
*/
8286
function __construct () {
87+
$this->lib = new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
8388
}
8489

8590
/**
@@ -100,15 +105,23 @@ function setOptions ($options) {
100105
$this->_options[$k] = $options[$k];
101106
}
102107

103-
if (!empty($options['jsAttrName'])) {
108+
if (isset($options['jsAttrName'])) {
104109
$this->_optJsAttrName = $options['jsAttrName'];
105110
}
106111

107-
if (!empty($options['jsAttrScheme'])) {
112+
if (isset($options['jsAttrScheme'])) {
108113
$this->_optJsAttrIsJs = $options['jsAttrScheme'] === 'js';
109114
}
110115

111-
if (!empty($options['escapeContent'])) {
116+
if (isset($options['jsCls'])) {
117+
$this->_optJsCls = $options['jsCls'];
118+
}
119+
120+
if (isset($options['clsNobaseMods'])) {
121+
$this->_optNobaseMods = true;
122+
}
123+
124+
if (isset($options['escapeContent'])) {
112125
$this->_optEscapeContent = $options['escapeContent'];
113126
}
114127

@@ -213,6 +226,52 @@ function match ($expr, $matcher = null) {
213226
return $this;
214227
}
215228

229+
/**
230+
* Объявляет глобальный шаблон, применяемый перед остальными.
231+
*
232+
* ```php
233+
* $bh->beforeEach(function ($ctx, $json) {
234+
* $ctx->attr('onclick', $json->counter);
235+
* });
236+
* ```
237+
*
238+
* @param Closure $matcher
239+
* @return BH
240+
*/
241+
function beforeEach ($matcher) {
242+
return $this->match('$before', $matcher);
243+
}
244+
245+
/**
246+
* Объявляет глобальный шаблон, применяемый после остальных.
247+
*
248+
* ```php
249+
* $bh->afterEach(function ($ctx) {
250+
* $ctx->tag('xdiv');
251+
* });
252+
* ```
253+
*
254+
* @param Closure $matcher
255+
* @return BH
256+
*/
257+
function afterEach ($matcher) {
258+
return $this->match('$after', $matcher);
259+
}
260+
261+
/**
262+
* Вставляет вызов шаблона в очередь вызова.
263+
*
264+
* @param Array $res
265+
* @param String $fnId
266+
* @param Number $index
267+
*/
268+
static protected function pushMatcher(&$res, $fnId, $index) {
269+
$res[] = (' $json->_m[' . $fnId . '] = true;');
270+
$res[] = (' $subRes = $ms[' . $index . ']["fn"]($ctx, $json);');
271+
$res[] = (' if ($subRes !== null) { return ($subRes ?: ""); }');
272+
$res[] = (' if ($json->_stop) return;');
273+
}
274+
216275
/**
217276
* Вспомогательный метод для компиляции шаблонов с целью их быстрого дальнейшего исполнения.
218277
* @return string
@@ -233,8 +292,19 @@ function buildMatcher () {
233292

234293
$res[] = 'return function ($ctx, $json) use ($ms) {';
235294

236-
$res[] = 'switch ($json->block ?: __undefined) {';
237295
$declByBlock = static::groupBy($declarations, 'block');
296+
297+
if (isset($declByBlock['$before'])) {
298+
foreach ($declByBlock['$before'] as $decl) {
299+
static::pushMatcher($res, $decl['__id'], $decl['index']);
300+
}
301+
}
302+
303+
$afterEach = isset($declByBlock['$after']) ? $declByBlock['$after'] : null;
304+
unset($declByBlock['$before'], $declByBlock['$after']);
305+
306+
if ($declByBlock) :
307+
$res[] = 'switch ($json->block ?: __undefined) {';
238308
foreach ($declByBlock as $blockName => $blockData) {
239309
$res[] = 'case "' . $blockName . '":';
240310

@@ -251,30 +321,35 @@ function buildMatcher () {
251321
if (isset($decl['elemMod'])) {
252322
$modKey = $decl['elemMod'];
253323
$conds[] = (
254-
'isset($json->mods) && $json->mods->{"' . $modKey . '"} === ' .
324+
'isset($json->elemMods) && $json->elemMods->{"' . $modKey . '"} === ' .
255325
($decl['elemModVal'] === true ? 'true' : '"' . $decl['elemModVal'] . '"'));
256326
}
257327
if (isset($decl['blockMod'])) {
258328
$modKey = $decl["blockMod"];
259329
$conds[] = (
260-
'isset($json->blockMods) && $json->blockMods->{"' . $modKey . '"} === ' .
330+
'isset($json->mods) && $json->mods->{"' . $modKey . '"} === ' .
261331
($decl['blockModVal'] === true ? 'true' : '"' . $decl['blockModVal'] . '"'));
262332
}
263333

264334
$res[] = (' if (' . join(' && ', $conds) . ') {');
265-
$res[] = (' $json->_m[' . $__id . '] = true;');
266-
$res[] = (' $subRes = $ms[' . $decl['index'] . ']["fn"]($ctx, $json);');
267-
$res[] = (' if ($subRes !== null) { return ($subRes ?: ""); }');
268-
$res[] = (' if ($json->_stop) return;');
335+
static::pushMatcher($res, $__id, $decl['index']);
269336
$res[] = (' }');
270337
}
271338

272-
$res[] = (' return;');
339+
$res[] = (' break;');
273340
}
274341
$res[] = ('}');
275-
$res[] = (' return;');
342+
$res[] = (' break;');
276343
}
277344
$res[] = ('}');
345+
endif;
346+
347+
if ($afterEach) {
348+
foreach ($afterEach as $decl) {
349+
static::pushMatcher($res, $decl['__id'], $decl['index']);
350+
}
351+
}
352+
278353
$res[] = ('};');
279354

280355
return "return function (\$ms) {\n" . join("\n", $res) . "\n};";
@@ -287,17 +362,13 @@ function getMatcher () {
287362
if ($this->_matcher) return $this->_matcher;
288363

289364
// debugging purposes only (!!!)
290-
// $debug = false; //true;
291-
292365
// $key = md5(join('|', array_map(function ($e) { return $e['expr']; }, $this->_matchers)));
293366
// $file = "./tmp/bh-matchers-{$key}.php";
294367
// $constructor = @include $file;
295368
// if (!$constructor) {
296-
// if ($debug) {
297369
$code = $this->buildMatcher();
298-
// file_put_contents($file, "<?php\n" . $code);
299-
// file_put_contents("./bh-matcher.php", "<?php\n" . $fn);
300-
// $constructor = include("./bh-matcher.php");
370+
// file_put_contents($file, "<?php\n" . $code);
371+
// $constructor = include $file;
301372
$constructor = eval($code);
302373
// }
303374

@@ -348,18 +419,14 @@ function processBemJson ($bemJson, $blockName = null, $ignoreContent = null) {
348419
}
349420

350421
$bemJson = $resultArr[0];
351-
$blockMods = null;
352-
if ($bemJson instanceof Json) {
353-
$blockMods = (!$bemJson->elem && isset($bemJson->mods)) ? $bemJson->mods : $bemJson->blockMods;
354-
}
355422

356423
$steps = [];
357424
$steps[] = new Step(
358425
$bemJson,
359426
$resultArr,
360427
0,
361428
$blockName,
362-
$blockMods
429+
null
363430
);
364431

365432
// var compiledMatcher = (this._fastMatcher || (this._fastMatcher = Function('ms', this.buildMatcher())(this._matchers)));
@@ -374,8 +441,8 @@ function processBemJson ($bemJson, $blockName = null, $ignoreContent = null) {
374441
// js: while (node = nodes.shift()) {
375442
while ($step = array_shift($steps)) {
376443
$json = $step->json;
377-
$blockName = $step->blockName;
378-
$blockMods = $step->blockMods;
444+
$blockName = $step->block;
445+
$blockMods = $step->mods;
379446

380447
if ($json instanceof JsonCollection) {
381448
$j = 0;
@@ -408,21 +475,19 @@ function processBemJson ($bemJson, $blockName = null, $ignoreContent = null) {
408475
continue;
409476

410477
} elseif ($json->elem) {
411-
$blockName = $json->block = isset($json->block) ? $json->block : $blockName;
412-
// sync mods:
413-
// blockMods = json.blockMods = json.blockMods || blockMods
414-
$blockMods = $json->blockMods = isset($json->blockMods) ? $json->blockMods : $blockMods;
415-
// sync elem mods:
416-
if (isset($json->elemMods)) {
417-
$json->mods = $json->elemMods;
478+
$blockName = $json->block = $json->block ?: $blockName;
479+
if (!isset($json->elemMods)) {
480+
$json->elemMods = $json->mods;
481+
$json->mods = null;
418482
}
483+
$blockMods = $json->mods = isset($json->mods) ? $json->mods : $blockMods;
419484

420485
} elseif ($json->block) {
421486
$blockName = $json->block;
422-
$blockMods = $json->blockMods = $json->mods;
487+
$blockMods = $json->mods;
423488
}
424489

425-
if ($json && $json->block) {
490+
if ($json instanceof Json) {
426491
if ($infiniteLoopDetection) {
427492
$json->_matcherCalls++;
428493
$this->_matcherCalls++;
@@ -441,8 +506,8 @@ function processBemJson ($bemJson, $blockName = null, $ignoreContent = null) {
441506
if ($subRes !== null) {
442507
$json = JsonCollection::normalize($subRes);
443508
$step->json = $json;
444-
$step->blockName = $blockName;
445-
$step->blockMods = $blockMods;
509+
$step->block = $blockName;
510+
$step->mods = $blockMods;
446511
$steps[] = $step;
447512
$stopProcess = true;
448513
}
@@ -451,34 +516,27 @@ function processBemJson ($bemJson, $blockName = null, $ignoreContent = null) {
451516

452517
if (!$stopProcess && $processContent && isset($json->content) && !is_scalar($json->content)) {
453518
$content = $json->content;
454-
//if ($content instanceof JsonCollection) {
455-
456-
$j = 0;
457-
foreach ($content as $i => $child) {
458-
if (is_scalar($child) || empty($child)) {
459-
continue;
460-
}
461-
462-
$steps[] = new Step(
463-
$child,
464-
$content,
465-
$i,
466-
$blockName,
467-
$blockMods,
468-
++$j,
469-
$step
470-
);
519+
520+
$j = 0;
521+
foreach ($content as $i => $child) {
522+
if (is_scalar($child) || empty($child)) {
523+
continue;
471524
}
472-
$content->_listLength = $j;
473525

474-
/*} else {
475-
// commented since 24 nov '14
476-
// throw new \Exception('Do we need it?');
477-
}*/
526+
$steps[] = new Step(
527+
$child,
528+
$content,
529+
$i,
530+
$blockName,
531+
$blockMods,
532+
++$j,
533+
$step
534+
);
535+
}
536+
$content->_listLength = $j;
478537
}
479538
}
480539

481-
//d('processBemjson#' . ($_callId) . ' out ', $resultArr[0]);
482540
return $resultArr[0];
483541
}
484542

@@ -528,15 +586,13 @@ public function toHtml ($json) {
528586

529587
$jsParams = false;
530588
if ($json->block) {
531-
$cls = static::toBemCssClasses($json, $base);
589+
$cls = static::toBemCssClasses($json, $base, null, $this->_optNobaseMods);
532590
if ($json->js !== null && $json->js !== false) {
533591
$jsParams = [];
534592
$jsParams[$base] = $json->js === true ? [] : $this->_filterNulls($json->js);
535593
}
536594
}
537595

538-
$addJSInitClass = $jsParams && !$json->elem;
539-
540596
if ($json->mix) {
541597
foreach ($json->mix as $mix) {
542598
if (!$mix || $mix->bem === false) {
@@ -553,18 +609,17 @@ public function toHtml ($json) {
553609
$mixElem = $mix->elem ?: ($mix->block ? null : ($json->block ? $json->elem : null));
554610
$mixBase = $mixBlock . ($mixElem ? '__' . $mixElem : '');
555611

556-
$cls .= static::toBemCssClasses($mix, $mixBase, $base);
612+
$cls .= static::toBemCssClasses($mix, $mixBase, $base, $this->_optNobaseMods);
557613
if ($mix->js !== null && $mix->js !== false) {
558614
$jsParams = $jsParams ?: [];
559615
$jsParams[$mixBase] = $mix->js === true ? [] : $this->_filterNulls($mix->js);
560616
$hasMixJsParams = true;
561-
if (!$addJSInitClass) $addJSInitClass = ($mixBlock && !$mixElem);
562617
}
563618
}
564619
}
565620

566621
if ($jsParams) {
567-
if ($addJSInitClass) $cls .= ' i-bem';
622+
if ($this->_optJsCls) $cls .= ' ' . $this->_optJsCls;
568623
$jsData = !$hasMixJsParams && $json->js === true ?
569624
'{&quot;' . $base . '&quot;:{}}' :
570625
self::attrEscape(str_replace('[]', '{}',
@@ -610,7 +665,7 @@ public static function attrEscape($s) {
610665
return htmlspecialchars($s, ENT_QUOTES);
611666
}
612667

613-
public static function toBemCssClasses($json, $base, $parentBase = null) {
668+
public static function toBemCssClasses($json, $base, $parentBase = null, $nobase = false) {
614669
$res = '';
615670

616671
if ($parentBase !== $base) {
@@ -620,14 +675,11 @@ public static function toBemCssClasses($json, $base, $parentBase = null) {
620675
$res .= $base;
621676
}
622677

623-
// if (mods = json.mods || json.elem && json.elemMods)
624-
$mods = isset($json->mods) ? $json->mods :
625-
($json->elem && isset($json->elemMods) ? $json->elemMods : null);
626-
if ($mods) {
627-
foreach ($mods as $k => $mod) {
628-
if ($mod || $mod === 0) {
629-
$res .= ' ' . $base . '_' . $k . ($mod === true ? '' : '_' . $mod);
630-
}
678+
// if (mods = json.elem && json.elemMods || json.mods)
679+
$mods = $json->elem && isset($json->elemMods) ? $json->elemMods : $json->mods;
680+
foreach ($mods as $k => $mod) {
681+
if ($mod || $mod === 0) {
682+
$res .= ' ' . ($nobase ? '' : $base) . '_' . $k . ($mod === true ? '' : '_' . $mod);
631683
}
632684
}
633685

0 commit comments

Comments
 (0)